Vlad Narizhnyi
11 months ago
49 changed files with 867 additions and 363 deletions
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
export enum GuestRouteKey { |
||||
Onboarding = 'Onboarding', |
||||
LanguageSelect = 'LanguageSelect', |
||||
Loading = 'Loading', |
||||
} |
@ -1,6 +1,10 @@
@@ -1,6 +1,10 @@
|
||||
export * from './route-keys.enum' |
||||
export * from './guest-route-keys.enum' |
||||
export * from './fonts.enum' |
||||
export * from './storage-key.enum' |
||||
export * from './language.enum' |
||||
export * from './products.enum'; |
||||
export * from './type-custom.enum'; |
||||
export * from './products.enum' |
||||
export * from './type-custom.enum' |
||||
export * from './guest-route-keys.enum' |
||||
export * from './nav-group.enum'; |
||||
export * from './user-route-keys.enum'; |
||||
|
||||
|
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
export enum NavGroup { |
||||
User = 'u', |
||||
Guest = 'g' |
||||
} |
@ -1,7 +1,8 @@
@@ -1,7 +1,8 @@
|
||||
export enum StorageKey { |
||||
OnBoarding = 'ONBOARDING_END', |
||||
Language = 'LANG_SELECTED', |
||||
FinishOnBoarding = 'FinishOnBoarding', |
||||
Language = 'Language', |
||||
Purchases = 'Purchases', |
||||
Products = 'Products', |
||||
CustomPackage = 'CustomPackage', |
||||
Players = 'Players', |
||||
} |
||||
|
@ -1,8 +1,6 @@
@@ -1,8 +1,6 @@
|
||||
export enum RouteKey { |
||||
Onboarding = 'Onboarding', |
||||
LanguageSelect = 'LanguageSelect', |
||||
export enum UserRouteKey { |
||||
Game = 'Game', |
||||
Loading = 'Loading', |
||||
Players = 'Players', |
||||
Packages = 'Packages', |
||||
TruthOrDare = 'TruthOrDare', |
||||
CustomPackage = 'CustomPackage', |
@ -1 +1,2 @@
@@ -1 +1,2 @@
|
||||
export * from './truth-or-dare-view' |
||||
export * from './player-field.component'; |
||||
|
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
import React, { FC } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
import { StyleSheet, TouchableOpacity } from 'react-native' |
||||
import { FormTextControll, Icon, colors } from '~module/common' |
||||
|
||||
interface IProps { |
||||
value: string |
||||
onChange: (val: string) => void |
||||
onDelete: () => void |
||||
} |
||||
|
||||
export const PlayerField: FC<IProps> = ({ value, onChange, onDelete }) => { |
||||
const { t } = useTranslation() |
||||
|
||||
const renderClear = () => ( |
||||
<TouchableOpacity style={styles.clearIcon} onPress={onDelete}> |
||||
<Icon name={'clear'} size={24} color={colors.textPrimary} /> |
||||
</TouchableOpacity> |
||||
) |
||||
|
||||
return ( |
||||
<FormTextControll |
||||
value={value} |
||||
onChange={onChange} |
||||
renderPostfix={renderClear} |
||||
inputProps={{ |
||||
placeholder: t('customPack.placeholder'), |
||||
placeholderTextColor: colors.darkPurple, |
||||
}} |
||||
inputStyle={styles.input} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
const styles = StyleSheet.create({ |
||||
input: { |
||||
borderRadius: 60, |
||||
backgroundColor: colors.lightPurple, |
||||
borderWidth: 0, |
||||
fontSize: 18, |
||||
color: colors.purple, |
||||
}, |
||||
clearIcon: { |
||||
position: 'absolute', |
||||
top: 3, |
||||
right: 10, |
||||
padding: 10, |
||||
}, |
||||
}) |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
import React, { FC } from 'react' |
||||
import { StyleSheet } from 'react-native' |
||||
import { Font, Txt } from '~module/common' |
||||
|
||||
interface IProps { |
||||
playerName: string |
||||
} |
||||
|
||||
export const PlayerName: FC<IProps> = ({ playerName }) => { |
||||
return <Txt style={styles.playerName}>{playerName}</Txt> |
||||
} |
||||
|
||||
const styles = StyleSheet.create({ |
||||
playerName: { |
||||
fontFamily: Font.Roboto700, |
||||
textAlign: 'center', |
||||
fontSize: 24, |
||||
lineHeight: 34, |
||||
}, |
||||
}) |
@ -1,2 +1,3 @@
@@ -1,2 +1,3 @@
|
||||
export * from './game.screen' |
||||
export * from './truth-or-dare.screen' |
||||
export * from './players.screen'; |
||||
|
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
import React, { FC } from 'react' |
||||
import { StyleSheet, View } from 'react-native' |
||||
import { |
||||
ButtonPrimary, |
||||
colors, |
||||
Font, |
||||
Header, |
||||
UserRouteKey, |
||||
ScreenLayout, |
||||
useNav, |
||||
ButtonWithIcon, |
||||
appEvents, |
||||
StorageKey, |
||||
useAppDispatch, |
||||
} from '../../common' |
||||
import { useTranslation } from 'react-i18next' |
||||
import { PlayerField } from '../components' |
||||
import AsyncStorage from '@react-native-async-storage/async-storage' |
||||
import { selectPlayers, setPlayers } from '~store/slices' |
||||
import { useSelector } from 'react-redux' |
||||
|
||||
export const PlayersScreen: FC = () => { |
||||
const { t } = useTranslation() |
||||
const nav = useNav() |
||||
const dispatch = useAppDispatch() |
||||
const players = useSelector(selectPlayers) |
||||
|
||||
const onAddField = () => { |
||||
dispatch(setPlayers([...players, ''])) |
||||
} |
||||
|
||||
const onChangePlayer = (index: number, value: string) => { |
||||
const updatedPlayers = [...players] |
||||
updatedPlayers[index] = value |
||||
dispatch(setPlayers(updatedPlayers)) |
||||
} |
||||
|
||||
const onDeletePlayer = (index: number) => { |
||||
const updatedPlayers = [...players] |
||||
|
||||
if (updatedPlayers.length === 1) return |
||||
|
||||
updatedPlayers.splice(index, 1) |
||||
dispatch(setPlayers(updatedPlayers)) |
||||
} |
||||
|
||||
const checkIsFullName = () => { |
||||
return players.some(playerName => !playerName) |
||||
} |
||||
|
||||
const onSavePlayers = async () => { |
||||
const hasEmptyName = checkIsFullName() |
||||
|
||||
if (hasEmptyName) { |
||||
return appEvents.emit('alert', { |
||||
title: 'Oops', |
||||
subtitle: |
||||
'It seems that you did not fill in all the players name. Please fill it.', |
||||
}) |
||||
} |
||||
|
||||
await AsyncStorage.setItem(StorageKey.Players, JSON.stringify(players)) |
||||
|
||||
nav.navigate(UserRouteKey.Packages) |
||||
} |
||||
|
||||
return ( |
||||
<ScreenLayout |
||||
needScroll |
||||
scrollStyle={{ |
||||
flex: 1, |
||||
paddingBottom: 40, |
||||
}} |
||||
headerComponent={ |
||||
<Header |
||||
title={t('pageTitles.players')} |
||||
onPressLeft={onSavePlayers} |
||||
/> |
||||
}> |
||||
<View style={{ rowGap: 16 }}> |
||||
{players.map((player, index) => ( |
||||
<PlayerField |
||||
key={index} |
||||
value={player} |
||||
onChange={value => onChangePlayer(index, value)} |
||||
onDelete={() => onDeletePlayer(index)} |
||||
/> |
||||
))} |
||||
</View> |
||||
|
||||
<ButtonWithIcon |
||||
styleBtn={styles.plusBtn} |
||||
iconName="add-plus" |
||||
onPress={onAddField} |
||||
/> |
||||
|
||||
<ButtonPrimary |
||||
iconName="play" |
||||
onPress={onSavePlayers} |
||||
style={styles.playBtn}> |
||||
{t('customPack.play')} |
||||
</ButtonPrimary> |
||||
</ScreenLayout> |
||||
) |
||||
} |
||||
|
||||
const styles = StyleSheet.create({ |
||||
playerName: { |
||||
fontFamily: Font.Roboto700, |
||||
textAlign: 'center', |
||||
marginBottom: 122, |
||||
}, |
||||
plusBtn: { |
||||
marginTop: 24, |
||||
borderRadius: 50, |
||||
width: 50, |
||||
height: 50, |
||||
backgroundColor: colors.purple, |
||||
marginLeft: 'auto', |
||||
}, |
||||
playBtn: { |
||||
marginTop: 'auto', |
||||
}, |
||||
}) |
@ -1 +1,2 @@
@@ -1 +1,2 @@
|
||||
export * from './user.group'; |
||||
export * from './quest.group'; |
||||
|
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
import React, { FC } from 'react' |
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack' |
||||
import { Platform } from 'react-native' |
||||
import { GuestRouteKey } from '~module/common' |
||||
import { LanguageSelectScreen, OnboardingScreen } from '../screens' |
||||
import { LoadingScreen } from '../screens/loading-screen' |
||||
|
||||
const GuestStack = createNativeStackNavigator() |
||||
|
||||
export const GuestNavigationGroup: FC = () => { |
||||
return ( |
||||
<GuestStack.Navigator |
||||
screenOptions={{ |
||||
headerShown: false, |
||||
animation: Platform.select({ |
||||
android: 'slide_from_right', |
||||
}), |
||||
}} |
||||
initialRouteName={GuestRouteKey.Loading}> |
||||
<GuestStack.Screen |
||||
name={GuestRouteKey.Loading} |
||||
component={LoadingScreen} |
||||
/> |
||||
<GuestStack.Screen |
||||
name={GuestRouteKey.LanguageSelect} |
||||
component={LanguageSelectScreen} |
||||
/> |
||||
<GuestStack.Screen |
||||
name={GuestRouteKey.Onboarding} |
||||
component={OnboardingScreen} |
||||
/> |
||||
</GuestStack.Navigator> |
||||
) |
||||
} |
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
export * from './language-select.screen' |
||||
export * from './on-boarding.screen' |
||||
export * from '../../settings/screens/settings.screen' |
||||
export * from './loading-screen'; |
||||
|
@ -0,0 +1,242 @@
@@ -0,0 +1,242 @@
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' |
||||
import firestore from '@react-native-firebase/firestore' |
||||
import { GameItem } from '../../module/common' |
||||
import { RootState } from '../store' |
||||
import _ from 'lodash' |
||||
|
||||
export interface GameItemsState { |
||||
gameItems: GameItem[] |
||||
shuffled: GameItem[] |
||||
loaded: boolean |
||||
hasError: boolean |
||||
} |
||||
|
||||
const initialState: GameItemsState = { |
||||
gameItems: [], |
||||
shuffled: [], |
||||
loaded: false, |
||||
hasError: false, |
||||
} |
||||
|
||||
const arr = [ |
||||
{ |
||||
id: 1, |
||||
isDare: false, |
||||
en: "What is the most outrageous thing you've ever done?", |
||||
ua: 'Яке найризикованіше рішення ти коли-небудь приймав?', |
||||
}, |
||||
{ |
||||
id: 2, |
||||
isDare: true, |
||||
en: 'Do your best dance moves for the next minute.', |
||||
ua: 'Станцюй свій найкрутіший танець протягом 20 секунд під музику.', |
||||
}, |
||||
{ |
||||
id: 3, |
||||
isDare: false, |
||||
en: "What's the most embarrassing thing you've Googled?", |
||||
ua: "Найсором'язливіше, що ти шукав в Google?", |
||||
}, |
||||
{ |
||||
id: 4, |
||||
isDare: true, |
||||
en: "Call a friend and sing 'Happy Birthday' to them, regardless of whether it's their birthday or not.", |
||||
ua: "Подзвони другові і заспівай 'З Днем Народження' незалежно від його дати народження.", |
||||
}, |
||||
{ |
||||
id: 5, |
||||
isDare: false, |
||||
en: "What's the most unusual food you've ever eaten?", |
||||
ua: 'Сама незвичайна їжа, яку ти коли-небудь їв(ла)?', |
||||
}, |
||||
{ |
||||
id: 6, |
||||
isDare: false, |
||||
en: 'If you could switch lives with someone for a day, who would it be and why?', |
||||
ua: 'Якщо б ти міг помінятися життям з кимось на один день, хто б це був і чому?', |
||||
}, |
||||
{ |
||||
id: 7, |
||||
isDare: true, |
||||
en: 'Take a funny selfie and post it on your social media.', |
||||
ua: 'Зроби смішне селфі і опублікуй його у своїх соціальних мережах.', |
||||
}, |
||||
{ |
||||
id: 8, |
||||
isDare: false, |
||||
en: "What's the most unusual food you've ever eaten?", |
||||
ua: 'Сама незвичайна їжа, яку ти коли-небудь їв(ла)?', |
||||
}, |
||||
{ |
||||
id: 9, |
||||
isDare: true, |
||||
en: "Send a text to your crush confessing your feelings (even if it's just for fun).", |
||||
ua: 'Напиши в особисті 10-му своєму підписнику "В мене погані новини для тебе" і не відповідай 24 години.', |
||||
}, |
||||
{ |
||||
id: 10, |
||||
isDare: false, |
||||
en: 'If you could have dinner with any historical figure, who would it be and what would you ask them?', |
||||
ua: 'Якщо б ви могли пообідати з будь-якою історичною постаттю, хто б це був і що ви б їх запитали?', |
||||
}, |
||||
{ |
||||
id: 11, |
||||
isDare: true, |
||||
en: 'Imitate a celebrity.', |
||||
ua: 'Імітуй якусь знаменитість.', |
||||
}, |
||||
{ |
||||
id: 12, |
||||
isDare: false, |
||||
en: 'If you had to eat one food for the rest of your life, what would it be?', |
||||
ua: 'Якщо б тобі довелося їсти одну їжу до кінця життя, що б це було?', |
||||
}, |
||||
{ |
||||
id: 14, |
||||
isDare: false, |
||||
en: "What's the most rebellious thing you did as a teenager?", |
||||
ua: 'Яке найбільш хуліганське ти зробив(ла) в дитинстві?', |
||||
}, |
||||
{ |
||||
id: 15, |
||||
isDare: true, |
||||
en: 'Create a silly rap about the person to your right.', |
||||
ua: 'Заспівай дурний реп про особу справа від тебе.', |
||||
}, |
||||
{ |
||||
id: 16, |
||||
isDare: false, |
||||
en: 'What is the weirdest talent you have?', |
||||
ua: 'Що ти вмієш робити найдивніше?', |
||||
}, |
||||
{ |
||||
id: 17, |
||||
isDare: true, |
||||
en: 'Act like a certain animal until your next turn. Others have to guess the animal.', |
||||
ua: 'Ведіть себе як собака до свого наступного ходу', |
||||
}, |
||||
{ |
||||
id: 18, |
||||
isDare: false, |
||||
en: 'If you could be invisible for a day, what would you do?', |
||||
ua: 'Якщо б ви могли бути невидимими на один день, що б ви зробили?', |
||||
}, |
||||
{ |
||||
id: 19, |
||||
isDare: true, |
||||
en: 'Send a strange sound to the third person on your social media friends list.', |
||||
ua: 'Відправ дивний звук третій людині, на яку ти підписаний(на)', |
||||
}, |
||||
{ |
||||
id: 20, |
||||
isDare: false, |
||||
en: 'What’s the most unusual job you can think of?', |
||||
ua: 'Найдурніша робота, яку б ти робив(ла) за хороші гроші?', |
||||
}, |
||||
{ |
||||
id: 21, |
||||
isDare: true, |
||||
en: 'Post something embarrassing on your social media for the next 30 minutes.', |
||||
ua: 'Добав в сторіз щось сором’язливе на 1 хв.', |
||||
}, |
||||
{ |
||||
id: 22, |
||||
isDare: false, |
||||
en: 'What’s the most embarrassing thing you’ve said in the heat of the moment?', |
||||
ua: 'Що ти "ляпав(ла)" не те в пориві емоцій?', |
||||
}, |
||||
{ |
||||
id: 23, |
||||
isDare: true, |
||||
en: 'Sing a song loudly and out of tune.', |
||||
ua: 'Співай що завгодно наступні 10 секунд', |
||||
}, |
||||
{ |
||||
id: 24, |
||||
isDare: false, |
||||
en: 'If you could time travel, which era would you visit and why?', |
||||
ua: 'Якщо б ти міг подорожувати в часі, яку епоху та місце ти б відвідав(ла) і чому?', |
||||
}, |
||||
{ |
||||
id: 25, |
||||
isDare: true, |
||||
en: 'Take a goofy selfie and set it as your profile picture for the next hour.', |
||||
ua: 'Зроби невдале селфі і постав на аву на 1 годину', |
||||
}, |
||||
{ |
||||
id: 26, |
||||
isDare: false, |
||||
en: 'What’s the most unusual place you’ve ever visited?', |
||||
ua: 'Яке найстрашніше місце, в якому ти був(ла)', |
||||
}, |
||||
{ |
||||
id: 27, |
||||
isDare: true, |
||||
en: 'Send a funny text to the fifth person in your contact list.', |
||||
ua: 'Відправ повідомлення "Я хочу зіграти з тобою в гру" 5-му в своєму списку контактів.', |
||||
}, |
||||
{ |
||||
id: 28, |
||||
isDare: false, |
||||
en: "What's the most daring thing you would do for a million dollars?", |
||||
ua: 'Ти б ходив ходив(ла) на протязі тижня голим(голою) на роботу за мільйон гривень?', |
||||
}, |
||||
{ |
||||
id: 29, |
||||
isDare: true, |
||||
en: 'What’s the most unexpected compliment you’ve ever received?', |
||||
ua: 'Який найбільш несподіваний комплімент тобі коли-небудь говорили?', |
||||
}, |
||||
{ |
||||
id: 30, |
||||
isDare: true, |
||||
en: 'Post something embarrassing on your social media for the next 30 minutes.', |
||||
ua: 'Добав в сторіз щось сором’язливе на 30 хв.', |
||||
}, |
||||
] |
||||
|
||||
export const getGameItemsFromFirestore = createAsyncThunk( |
||||
'game-items/get-from-firestore', |
||||
async () => { |
||||
const query = await firestore().collection('light').get() |
||||
|
||||
const response: any = query.docs.map(doc => doc.data()) |
||||
|
||||
const truthsAndDares: GameItem[] = response[0]['data'] |
||||
|
||||
return truthsAndDares |
||||
}, |
||||
) |
||||
|
||||
export const postsSlice = createSlice({ |
||||
name: 'gameItems', |
||||
initialState, |
||||
reducers: { |
||||
shuffleItems: state => { |
||||
state.shuffled = _.shuffle(state.gameItems) |
||||
}, |
||||
}, |
||||
extraReducers(builder) { |
||||
builder |
||||
.addCase(getGameItemsFromFirestore.fulfilled, (state, action) => { |
||||
state.gameItems = action.payload |
||||
state.hasError = false |
||||
state.loaded = true |
||||
}) |
||||
.addCase(getGameItemsFromFirestore.pending, state => { |
||||
state.loaded = false |
||||
}) |
||||
.addCase(getGameItemsFromFirestore.rejected, state => { |
||||
state.hasError = true |
||||
}) |
||||
}, |
||||
}) |
||||
|
||||
export const { shuffleItems } = postsSlice.actions |
||||
|
||||
export const selectGameItems = (state: RootState) => state.gameItems.gameItems |
||||
export const selectHasError = (state: RootState) => state.gameItems.hasError |
||||
export const selectLoaded = (state: RootState) => state.gameItems.loaded |
||||
export const selectShuffled = (state: RootState) => state.gameItems.shuffled |
||||
|
||||
export default postsSlice.reducer |
@ -1,3 +1,5 @@
@@ -1,3 +1,5 @@
|
||||
export * from './current-step-slice' |
||||
export * from './posts.slice' |
||||
export * from './custom-package.slice'; |
||||
export * from './current-step.slice' |
||||
export * from './game-items.slice' |
||||
export * from './custom-package.slice' |
||||
export * from './navigation.slice' |
||||
export * from './players.slice' |
||||
|
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit' |
||||
import { RootState } from '../store' |
||||
import { NavGroup } from '~module/common' |
||||
|
||||
interface NavGroupState { |
||||
navGroup: NavGroup |
||||
} |
||||
|
||||
const initialState: NavGroupState = { |
||||
navGroup: NavGroup.Guest, |
||||
} |
||||
|
||||
export const NavGroupSlice = createSlice({ |
||||
name: 'navGroup', |
||||
initialState, |
||||
reducers: { |
||||
setNavGroup: (state, action: PayloadAction<NavGroup>) => { |
||||
state.navGroup = action.payload |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
export const { setNavGroup } = NavGroupSlice.actions |
||||
|
||||
export const selectNavGroup = (state: RootState) => state.navGroup.navGroup |
||||
|
||||
export default NavGroupSlice.reducer |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit' |
||||
import { StorageKey } from '../../module/common' |
||||
import { RootState } from '../store' |
||||
import _ from 'lodash' |
||||
import AsyncStorage from '@react-native-async-storage/async-storage' |
||||
|
||||
export interface PlayersState { |
||||
players: string[] |
||||
playerIndex: number |
||||
currentPlayer: string |
||||
loaded: boolean |
||||
hasError: boolean |
||||
} |
||||
|
||||
const initialState: PlayersState = { |
||||
players: [], |
||||
playerIndex: 0, |
||||
currentPlayer: '', |
||||
loaded: false, |
||||
hasError: false, |
||||
} |
||||
|
||||
export const getPlayersAsync = createAsyncThunk('get-players', async () => { |
||||
const savedPlayers = await AsyncStorage.getItem(StorageKey.Players) |
||||
|
||||
return savedPlayers ? JSON.parse(savedPlayers) : [''] |
||||
}) |
||||
|
||||
export const playersSlice = createSlice({ |
||||
name: 'players', |
||||
initialState, |
||||
reducers: { |
||||
setPlayers: (state, action: PayloadAction<string[]>) => { |
||||
state.players = action.payload |
||||
}, |
||||
onNextPlayer: state => { |
||||
state.playerIndex = state.playerIndex + 1 |
||||
const player = state.players[state.playerIndex] |
||||
|
||||
if (!player) { |
||||
state.currentPlayer = state.players[0] |
||||
state.playerIndex = 0 |
||||
return |
||||
} |
||||
|
||||
state.currentPlayer = player |
||||
}, |
||||
}, |
||||
extraReducers(builder) { |
||||
builder |
||||
.addCase(getPlayersAsync.fulfilled, (state, action) => { |
||||
state.players = action.payload |
||||
state.currentPlayer = state.players[0] |
||||
}) |
||||
.addCase(getPlayersAsync.pending, state => { |
||||
state.loaded = false |
||||
}) |
||||
.addCase(getPlayersAsync.rejected, state => { |
||||
state.hasError = true |
||||
}) |
||||
}, |
||||
}) |
||||
|
||||
export const { setPlayers, onNextPlayer } = playersSlice.actions |
||||
|
||||
export const selectPlayers = (state: RootState) => state.players.players |
||||
export const selectCurrentPlayer = (state: RootState) => |
||||
state.players.currentPlayer |
||||
|
||||
export default playersSlice.reducer |
@ -1,65 +0,0 @@
@@ -1,65 +0,0 @@
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' |
||||
import firestore from '@react-native-firebase/firestore' |
||||
import { GameItem } from '../../module/common' |
||||
import { RootState } from '../store' |
||||
import _ from 'lodash' |
||||
|
||||
export interface PostsState { |
||||
posts: GameItem[] |
||||
shuffled: GameItem[] |
||||
loaded: boolean |
||||
hasError: boolean |
||||
} |
||||
|
||||
const initialState: PostsState = { |
||||
posts: [], |
||||
shuffled: [], |
||||
loaded: false, |
||||
hasError: false, |
||||
} |
||||
|
||||
export const fetchPostsAsync = createAsyncThunk( |
||||
'posts/fetchPosts', |
||||
async () => { |
||||
const querySnapshot = await firestore().collection('GameItems').get() |
||||
|
||||
const data: GameItem[] = querySnapshot.docs.map( |
||||
doc => doc.data() as GameItem, |
||||
) |
||||
|
||||
return data |
||||
}, |
||||
) |
||||
|
||||
export const postsSlice = createSlice({ |
||||
name: 'posts', |
||||
initialState, |
||||
reducers: { |
||||
shuffleItems: state => { |
||||
state.shuffled = _.shuffle(state.posts) |
||||
}, |
||||
}, |
||||
extraReducers(builder) { |
||||
builder |
||||
.addCase(fetchPostsAsync.fulfilled, (state, action) => { |
||||
state.posts = action.payload |
||||
state.hasError = false |
||||
state.loaded = true |
||||
}) |
||||
.addCase(fetchPostsAsync.pending, state => { |
||||
state.loaded = false |
||||
}) |
||||
.addCase(fetchPostsAsync.rejected, state => { |
||||
state.hasError = true |
||||
}) |
||||
}, |
||||
}) |
||||
|
||||
export const { shuffleItems } = postsSlice.actions |
||||
|
||||
export const selectPosts = (state: RootState) => state.gameItems.posts |
||||
export const selectHasError = (state: RootState) => state.gameItems.hasError |
||||
export const selectLoaded = (state: RootState) => state.gameItems.loaded |
||||
export const selectShuffled = (state: RootState) => state.gameItems.shuffled |
||||
|
||||
export default postsSlice.reducer |
Loading…
Reference in new issue