import { type PayloadAction, createSlice } from '@reduxjs/toolkit' import { type UserType } from '../types' import { createAppAsyncThunk } from './hooks' import type { ResponseData } from '../network' import { isActionPending, isActionRejected, token, type PendingAction, type RejectedAction, } from '../utils' type UserState = { user: UserType loading: boolean error?: string } const defaultUser = (): UserType => { return { id: -1, nickname: '', email: '', token: token.load(), token_expiry_date: '', money: '', histories_id: [], } } const initialState = (): UserState => { return { user: defaultUser(), loading: false, error: undefined, } } const userSlice = createSlice({ name: 'user', initialState: initialState(), reducers: { setUser: (state, action: PayloadAction) => { state.user = action.payload }, clearUser() { token.save('') return initialState() }, clearError: state => { state.error = '' }, }, extraReducers: builder => { builder .addCase(fetchUser.fulfilled, (state, action) => { state.user = action.payload.success?.[0] as UserType state.loading = false }) .addCase(fetchLogin.fulfilled, (state, action) => { state.user = action.payload.success?.[0] as UserType state.loading = false state.error = undefined token.save(state.user.token) }) .addCase(fetchLogout.fulfilled, state => { if (state.user?.token) { state.user.token = '' } state.loading = false state.error = undefined token.save('') }) .addCase(fetchRegister.fulfilled, (state, action) => { state.user = action.payload.success?.[0] as UserType state.loading = false state.error = undefined token.save(state.user.token) }) .addCase(fetchUnregister.fulfilled, (state, action) => { state.user = action.payload.success?.[0] as UserType state.loading = false state.error = undefined token.save(state.user.token) }) .addCase(fetchAddMoney.fulfilled, (state, action) => { state.user = action.payload.success?.[0] as UserType state.loading = false state.error = undefined token.save(state.user.token) }) .addMatcher(isActionPending('user'), state => { state.loading = true state.error = undefined }) .addMatcher(isActionRejected('user'), (state, action) => { state.loading = false if (action.payload) { state.error = (action.payload as ResponseData).error } else { state.error = action.error.message } }) }, selectors: { accessToken: (state: UserState) => state.user?.token, user: (state: UserState) => state.user, loading: (state: UserState) => state.loading, error: (state: UserState) => state.error, }, }) const fetchUser = createAppAsyncThunk( `${userSlice.name}/fetchUser`, async (_, { fulfillWithValue, rejectWithValue, extra: networkApi }) => { try { const data = await networkApi.request('/user/', { method: 'POST', }) return fulfillWithValue(data) } catch (error) { return rejectWithValue(error) } } ) type RegisterProps = { email: string nickname: string password: string } const fetchRegister = createAppAsyncThunk( `${userSlice.name}/fetchRegister`, async (props, { fulfillWithValue, rejectWithValue, extra: networkApi }) => { try { const data = await networkApi.request('/user/', { method: 'POST', body: JSON.stringify({ register: props }), }) return fulfillWithValue(data) } catch (error) { return rejectWithValue(error) } } ) const fetchUnregister = createAppAsyncThunk( `${userSlice.name}/fetchUnregister`, async (_, { fulfillWithValue, rejectWithValue, extra: networkApi }) => { try { const data = await networkApi.request('/user/', { method: 'POST', body: JSON.stringify({ unregister: {} }), }) return fulfillWithValue(data) } catch (error) { return rejectWithValue(error) } } ) type LoginProps = { email: string password: string } const fetchLogin = createAppAsyncThunk( `${userSlice.name}/fetchLogin`, async (props, { fulfillWithValue, rejectWithValue, extra: networkApi }) => { try { const data = await networkApi.request('/user/', { method: 'POST', body: JSON.stringify({ login: props }), }) return fulfillWithValue(data) } catch (error) { return rejectWithValue(error) } } ) const fetchLogout = createAppAsyncThunk( `${userSlice.name}/fetchLogout`, async (_, { fulfillWithValue, rejectWithValue, extra: networkApi }) => { try { const data = await networkApi.request('/user/', { method: 'POST', body: JSON.stringify({ logout: {} }), }) return fulfillWithValue(data) } catch (error) { return rejectWithValue(error) } } ) type AddMoneyProps = { money: string } const fetchAddMoney = createAppAsyncThunk( `${userSlice.name}/fetchAddMoney`, async (props, { fulfillWithValue, rejectWithValue, extra: networkApi }) => { try { const data = await networkApi.request('/user/', { method: 'POST', body: JSON.stringify({ add_money: props }), }) return fulfillWithValue(data) } catch (error) { return rejectWithValue(error) } } ) class User { selector = { ...userSlice.selectors } action = userSlice.actions reducer = userSlice.reducer name = userSlice.name fetch = { user: fetchUser, login: fetchLogin, logout: fetchLogout, register: fetchRegister, unregister: fetchUnregister, addMoney: fetchAddMoney, } } export const user = new User()