diff --git a/src/App.css b/src/App.css index 01cc586..5730ebf 100644 --- a/src/App.css +++ b/src/App.css @@ -1,39 +1,12 @@ .App { text-align: center; + margin: 0; + padding: 0; } -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-float infinite 3s ease-in-out; - } -} - -.App-header { - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); -} - -.App-link { - color: rgb(112, 76, 182); -} - -@keyframes App-logo-float { - 0% { - transform: translateY(0); - } - 50% { - transform: translateY(10px); - } - 100% { - transform: translateY(0px); - } -} +.navbar { + display: inline-flex; + flex-direction: row; + justify-content: space-between; + align-items: baseline; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index a40f66d..99c00b2 100644 --- a/src/App.js +++ b/src/App.js @@ -1,58 +1,15 @@ import React from 'react'; -import logo from './logo.svg'; -import { Counter } from './features/counter/Counter'; import './App.css'; +import Navbar from './features/navbar/Navbar'; +import redditSlice from './features/reddit/redditSlice'; function App() { return (
-
- logo - -

- Edit src/App.js and save to reload. -

- - Learn - - React - - , - - Redux - - , - - Redux Toolkit - - , and - - React Redux - - -
+ +

Stuff

); } -export default App; +export default App; \ No newline at end of file diff --git a/src/App.test.js b/src/App.test.js index 659cc13..f6847b2 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -4,12 +4,16 @@ import { Provider } from 'react-redux'; import { store } from './app/store'; import App from './App'; -test('renders learn react link', () => { +test('renders text', () => { const { getByText } = render( ); - expect(getByText(/learn/i)).toBeInTheDocument(); + expect(getByText(/Stuff/)).toBeInTheDocument(); }); + +test('store is not empty or falsy', () => { + expect(store).not.toBeNull(); +}) \ No newline at end of file diff --git a/src/app/store.js b/src/app/store.js index 9eca6d2..382977d 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -1,8 +1,10 @@ import { configureStore } from '@reduxjs/toolkit'; -import counterReducer from '../features/counter/counterSlice'; +import postsSlice from '../features/posts/postsSlice'; +import redditSlice from '../features/reddit/redditSlice'; export const store = configureStore({ reducer: { - counter: counterReducer, + redditSlice: redditSlice, + postsSlice: postsSlice, }, }); diff --git a/src/features/counter/Counter.js b/src/features/counter/Counter.js deleted file mode 100644 index 772a6ba..0000000 --- a/src/features/counter/Counter.js +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { - decrement, - increment, - incrementByAmount, - incrementAsync, - incrementIfOdd, - selectCount, -} from './counterSlice'; -import styles from './Counter.module.css'; - -export function Counter() { - const count = useSelector(selectCount); - const dispatch = useDispatch(); - const [incrementAmount, setIncrementAmount] = useState('2'); - - const incrementValue = Number(incrementAmount) || 0; - - return ( -
-
- - {count} - -
-
- setIncrementAmount(e.target.value)} - /> - - - -
-
- ); -} diff --git a/src/features/counter/Counter.module.css b/src/features/counter/Counter.module.css deleted file mode 100644 index 004ae33..0000000 --- a/src/features/counter/Counter.module.css +++ /dev/null @@ -1,78 +0,0 @@ -.row { - display: flex; - align-items: center; - justify-content: center; -} - -.row > button { - margin-left: 4px; - margin-right: 8px; -} -.row:not(:last-child) { - margin-bottom: 16px; -} - -.value { - font-size: 78px; - padding-left: 16px; - padding-right: 16px; - margin-top: 2px; - font-family: 'Courier New', Courier, monospace; -} - -.button { - appearance: none; - background: none; - font-size: 32px; - padding-left: 12px; - padding-right: 12px; - outline: none; - border: 2px solid transparent; - color: rgb(112, 76, 182); - padding-bottom: 4px; - cursor: pointer; - background-color: rgba(112, 76, 182, 0.1); - border-radius: 2px; - transition: all 0.15s; -} - -.textbox { - font-size: 32px; - padding: 2px; - width: 64px; - text-align: center; - margin-right: 4px; -} - -.button:hover, -.button:focus { - border: 2px solid rgba(112, 76, 182, 0.4); -} - -.button:active { - background-color: rgba(112, 76, 182, 0.2); -} - -.asyncButton { - composes: button; - position: relative; -} - -.asyncButton:after { - content: ''; - background-color: rgba(112, 76, 182, 0.15); - display: block; - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; - opacity: 0; - transition: width 1s linear, opacity 0.5s ease 1s; -} - -.asyncButton:active:after { - width: 0%; - opacity: 1; - transition: 0s; -} diff --git a/src/features/counter/counterAPI.js b/src/features/counter/counterAPI.js deleted file mode 100644 index cc9b4a4..0000000 --- a/src/features/counter/counterAPI.js +++ /dev/null @@ -1,6 +0,0 @@ -// A mock function to mimic making an async request for data -export function fetchCount(amount = 1) { - return new Promise((resolve) => - setTimeout(() => resolve({ data: amount }), 500) - ); -} diff --git a/src/features/counter/counterSlice.js b/src/features/counter/counterSlice.js deleted file mode 100644 index 8dc4b5c..0000000 --- a/src/features/counter/counterSlice.js +++ /dev/null @@ -1,73 +0,0 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import { fetchCount } from './counterAPI'; - -const initialState = { - value: 0, - status: 'idle', -}; - -// The function below is called a thunk and allows us to perform async logic. It -// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This -// will call the thunk with the `dispatch` function as the first argument. Async -// code can then be executed and other actions can be dispatched. Thunks are -// typically used to make async requests. -export const incrementAsync = createAsyncThunk( - 'counter/fetchCount', - async (amount) => { - const response = await fetchCount(amount); - // The value we return becomes the `fulfilled` action payload - return response.data; - } -); - -export const counterSlice = createSlice({ - name: 'counter', - initialState, - // The `reducers` field lets us define reducers and generate associated actions - reducers: { - increment: (state) => { - // Redux Toolkit allows us to write "mutating" logic in reducers. It - // doesn't actually mutate the state because it uses the Immer library, - // which detects changes to a "draft state" and produces a brand new - // immutable state based off those changes - state.value += 1; - }, - decrement: (state) => { - state.value -= 1; - }, - // Use the PayloadAction type to declare the contents of `action.payload` - incrementByAmount: (state, action) => { - state.value += action.payload; - }, - }, - // The `extraReducers` field lets the slice handle actions defined elsewhere, - // including actions generated by createAsyncThunk or in other slices. - extraReducers: (builder) => { - builder - .addCase(incrementAsync.pending, (state) => { - state.status = 'loading'; - }) - .addCase(incrementAsync.fulfilled, (state, action) => { - state.status = 'idle'; - state.value += action.payload; - }); - }, -}); - -export const { increment, decrement, incrementByAmount } = counterSlice.actions; - -// The function below is called a selector and allows us to select a value from -// the state. Selectors can also be defined inline where they're used instead of -// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)` -export const selectCount = (state) => state.counter.value; - -// We can also write thunks by hand, which may contain both sync and async logic. -// Here's an example of conditionally dispatching actions based on current state. -export const incrementIfOdd = (amount) => (dispatch, getState) => { - const currentValue = selectCount(getState()); - if (currentValue % 2 === 1) { - dispatch(incrementByAmount(amount)); - } -}; - -export default counterSlice.reducer; diff --git a/src/features/counter/counterSlice.spec.js b/src/features/counter/counterSlice.spec.js deleted file mode 100644 index c1fed2c..0000000 --- a/src/features/counter/counterSlice.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import counterReducer, { - increment, - decrement, - incrementByAmount, -} from './counterSlice'; - -describe('counter reducer', () => { - const initialState = { - value: 3, - status: 'idle', - }; - it('should handle initial state', () => { - expect(counterReducer(undefined, { type: 'unknown' })).toEqual({ - value: 0, - status: 'idle', - }); - }); - - it('should handle increment', () => { - const actual = counterReducer(initialState, increment()); - expect(actual.value).toEqual(4); - }); - - it('should handle decrement', () => { - const actual = counterReducer(initialState, decrement()); - expect(actual.value).toEqual(2); - }); - - it('should handle incrementByAmount', () => { - const actual = counterReducer(initialState, incrementByAmount(2)); - expect(actual.value).toEqual(5); - }); -}); diff --git a/src/features/navbar/Navbar.js b/src/features/navbar/Navbar.js new file mode 100644 index 0000000..2990440 --- /dev/null +++ b/src/features/navbar/Navbar.js @@ -0,0 +1,11 @@ +import React from "react"; + +export default function Navbar() { + return ( +
+

Reddit but it's all cats

+

Search bar here

+

Expand sidebar here

+
+ ) +} \ No newline at end of file diff --git a/src/features/posts/Post.js b/src/features/posts/Post.js new file mode 100644 index 0000000..e69de29 diff --git a/src/features/posts/postsSlice.js b/src/features/posts/postsSlice.js new file mode 100644 index 0000000..9413cee --- /dev/null +++ b/src/features/posts/postsSlice.js @@ -0,0 +1,50 @@ +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; + +export const fetchBySub = createAsyncThunk( + 'reddit/fetchBySub', + async(subreddit) => { // expects an argument corresponding to the url, in json format, of a given subreddit + try { + const myRequest = new Request(subreddit); // initializes request + let response = await fetch(myRequest); + let json = await response.json(); + let postsArray = json.data.children; // unpacks individual post objects from the subreddit JSON file, as an array + return postsArray; + } catch(e) { + console.log(e); + } + } +); + +export const postsSlice = createSlice({ + name: 'posts', + initialState: { + posts: [], + requestsPending: false, + requestDenied: false, + }, + reducers: { + filterPosts(state,action) { // Expects action.payload to be the searchterm imported from the state of searchBar + state.posts.filter(post => (post.data.title !== action.payload) && (post.data.selftext !== action.payload)); + } + }, + extraReducers: (builder) => { + builder.addCase(fetchBySub.pending, (state,action) => { + state.requestsPending = true; + state.requestDenied = false; + }) + builder.addCase(fetchBySub.rejected, (state,action) => { + state.requestsPending = false; + state.requestDenied = true; + }) + builder.addCase(fetchBySub.fulfilled, (state,action) => { + state.requestsPending = false; + state.requestDenied = false; + for (let sub in action.payload) { // iterates over postsArray to avoid + state.posts.push(sub); // nesting arrays within the state's posts + } + }) + } +}); + +export default postsSlice.reducer; +export const { filterPosts } = postsSlice.actions; \ No newline at end of file diff --git a/src/features/reddit/redditSlice.js b/src/features/reddit/redditSlice.js new file mode 100644 index 0000000..ae2524f --- /dev/null +++ b/src/features/reddit/redditSlice.js @@ -0,0 +1,76 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const urlBase = 'https://www.reddit.com/'; + +export const redditSlice = createSlice({ + name: 'redditSlice', + initialState: { + subreddits: { + 'r/cats': { + name: 'r/cats', + access: `${urlBase}r/cats.json`, + isSelected: true + }, + 'r/IllegallySmolCats': { + name: 'r/IllegallySmolCats', + access: `${urlBase}r/IllegallySmolCats.json`, + isSelected: true + }, + 'r/Catswhoyell': { + name: 'r/Catswhoyell', + access: `${urlBase}r/Catswhoyell.json`, + isSelected: true + }, + 'r/ActivationSound': { + name: 'r/ActivationSound', + access: `${urlBase}r/ActivationSound.json`, + isSelected: true, + }, + 'r/CatSlaps': { + name: 'r/CatSlaps', + access: `${urlBase}r/CatSlaps.json`, + isSelected: true + }, + 'r/CatTaps': { + name: 'r/CatTaps', + access: `${urlBase}r/CatTaps.json`, + isSelected: true + }, + 'r/catsinboxes': { + name: 'r/catsinboxes', + access: `${urlBase}r/catsinboxes.json`, + isSelected: true, + }, + 'r/Thisismylifemeow': { + name: 'r/Thisismylifemeow', + access: `${urlBase}r/Thisismylifemeow.json`, + isSelected: true + }, + 'r/scrungycats': { + name: 'r/scrungycats', + access: `${urlBase}r/scrungycats.json`, + isSelected: true, + }, + 'r/notmycat': { + name: 'r/notmycat', + access: `${urlBase}r/notmycat.json`, + isSelected: true, + }, + 'r/StartledCats': { + name: 'r/StartledCats', + access: `${urlBase}r/StartledCats.json`, + isSelected: true + } + }, + }, + reducers: { + updateSubVisibility(state,action) { + // reads state of buttons in Sidebar component to determine whether each is active + // connects with post rendering, filtering out posts belonging to inactive subreddits + } + }, + extraReducers: {}, +}); + +export default redditSlice.reducer; +export const { updateSubVisibility } = redditSlice.actions; \ No newline at end of file diff --git a/src/features/reddit/redditSlice.test.js b/src/features/reddit/redditSlice.test.js new file mode 100644 index 0000000..e69de29 diff --git a/src/features/searchBar/searchBar.js b/src/features/searchBar/searchBar.js new file mode 100644 index 0000000..e69de29 diff --git a/src/features/sidebar/Sidebar.js b/src/features/sidebar/Sidebar.js new file mode 100644 index 0000000..e69de29