diff --git a/src/App.js b/src/App.js index 3058f6f3..fbe72b81 100644 --- a/src/App.js +++ b/src/App.js @@ -22,4 +22,12 @@ function App() { ); } -export default App; +const mapStateToProps = (state)=>{ + return{}; +}; +const mapDispatchToProps=(dispatch)=>({ + getUserAuth: ()=>dispatch(getUserAuth()), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(App); + diff --git a/src/actions/actionType.js b/src/actions/actionType.js new file mode 100644 index 00000000..38aacb45 --- /dev/null +++ b/src/actions/actionType.js @@ -0,0 +1,3 @@ +export const SET_USER ='SET_USER'; +export const SET_LOADING_STATUS ='SET_LOADING_STATUS'; +export const GET_ARTICLES ='GET_ARTICLES'; \ No newline at end of file diff --git a/src/actions/index.js b/src/actions/index.js new file mode 100644 index 00000000..41aae944 --- /dev/null +++ b/src/actions/index.js @@ -0,0 +1,109 @@ +import {auth,provider,storage} from "../firebase"; +import {SET_USER, SET_LOADING_STATUS, GET_ARTICLES} from "../actions/actionType"; +import db from "../firebase"; + + +export const setUser = (payload) =>({ + type: SET_USER, + user:payload, +}); +export const setLoading =(status) =>({ + type: SET_LOADING_STATUS, + status:status, +}); + +export const getArticles=(payload) =>({ + type: GET_ARTICLES, + payload:payload, +}) + +export function signInAPI(){ + return(dispatch)=>{ + auth.signInWithPopup(provider) + .then((payload) =>{ + dispatch(setUser(payload.user)); + }) + .catch((error) => alert(error.message)); + }; +} + +export function getUserAuth(){ + return(dispatch)=>{ + auth.onAuthStateChanged(async(user)=>{ + if(user){ + dispatch(setUser(user)); + } + }) + } +} +export function signOutAPI() { + return (dispatch) => { + auth.signOut().then(()=>{ + dispatch(setUser(null)); + }).catch(error => alert(error.message)); + }; +} + +export function postArticleAPI(payload){ + return (dispatch) => { + dispatch(setLoading(true)); + if(payload.image !="") { + const upload= storage + .ref('image/${payload.image.name}') + .put(payload.image); + upload.on( + 'state_Changed',(snapshot) => { + const progress= (snapshot.bytesTransferred/ snapshot.totalBytes)*100; + console.log('Progress: ${progress}%'); + if(snapshot.state === "RUNNING"){ + console.log('Progress: ${progress}%'); + } + }, + (error) => console.log(error.code), + async () =>{ + const downloadURL = await upload.snapshot.ref.getDownloadURL(); + db.collection("articles").add({ + actor:{ + description:payload.user.email, + title:payload.user.displayName, + date:payload.timestamp, + image:payload.user.photoURL, + }, + video:payload.video, + sharedImg: downloadURL, + comment:0, + description:payload.description, + }); + dispatch(setLoading(false)); + } + ); + } else if (payload.video){ + db.collection("articles").add({ + actor:{ + description:payload.user.email, + title: payload.user.displayName, + date: payload.timestamp, + image: payload.user.photoURL, + }, + video:payload.video, + sharedImg:" ", + comment:0, + description:payload.description, + }); + dispatch(setLoading(false)); + } + }; + +} +export function getArticlesAPI(){ + return(dispatch)=>{ + let payload; + + db.collection("articles") + .orderBy("actor.date","desc") + .onSnapshot((snapshot)=>{ + payload = snapshot.docs.map((doc)=>doc.data()); + dispatch(getArticles(payload)); + }); + } +} diff --git a/src/components/Main.js b/src/components/Main.js index fd6e2d54..d9d7079e 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -1,11 +1,400 @@ import styled from "styled-components"; +import {useEffect,useState} from "react"; +import PostModal from "./PostModal"; +import {connect} from "react-redux"; +import {getArticlesAPI} from "../actions"; +import ReactPlayer from "react-player"; + const Main = (props) => { - return Main; + + const[showModal, setShowModal] = useState("close"); + + useEffect(() =>{ + props.getArticles(); + },[]); + + const handleClick = (e) => { + e.preventDefault(); + if(e.target !== e.currentTarget){ + return; + } + switch(showModal){ + case "open": + setShowModal("close"); + break; + case "close": + setShowModal("open"); + break; + default: + setShowModal("close"); + break; + } + }; + + return( + <> + { + props.articles.length === 0 ? ( +

There are no articles

+ ) : ( + + +
+ { props.user && props.user.photoURL ? ( + + ):( + + + )} + +
+ +
+ + + + + + + + + +
+
+ + {props.loading && } + {props.articles.length > 0 && + props.articles.map((article, key) => ( + + ))} + + +
+ )} + + ); }; const Container = styled.div` grid-area: main; `; -export default Main; + +const CommonCard = styled.div` + text-align: center; + overflow: hidden; + margin-bottom: 8px; + background-color: #fff; + border-radius:5px; + possition:relative; + border:none; + box-shadow: 0 0 0 1px rgba(0 0 0 / 15%), 0 0 0 1px rgba(0 0 / 20%); +`; + + const ShareBox= styled(CommonCard)` + diaply:flex; + flex-direction: column; + color: #958b7b; + margin: 0 0 8px; + background: white; + + div { + button{ + outline: none; + color: rgba(0, 0, 0, 0.6); + font-size:14px; + line-height: 1.5; + min-height: 48px; + background: transparent; + border: none; + display:flex; + align-items: center; + font-weight: 600; + + } + &:first-child { + display: flex; + align-items: center; + padding: 8px 16px 0px 16px; + img { + width:48px; + border-radius:50%; + margin-right: 8px; + } + button{ + margin:4px 0 0 0; + flex-grow:1; + border-radius: 35px; + padding-left:16; + border: 1px solid rgba(0, 0, 0, 0.15); + background-color:white; + text-align:left; + } + } + &:nth-child(2){ + display: flex; + flex-wrap:wrap; + justify-content:space-around; + padding-bottom:4px; + padding-top:4px; + + + button{ + img{ + margin: 0 4px 0 -2px; + width:25px; + height:25px; + + } + } + } + } + + `; + + const Article = styled(CommonCard)` + padding:0; + margin: 0 0 8px 0; + overflow:visible; + `; + + const SharedActor = styled.div` + paddinh-right=40px; + flex-wrap:nowrap; + padding:12px 16px 0; + margin-bottom:8px; + align-items:center; + display:flex; + a{ + margin-right:12px; + flex-grow:1; + overflow:hidden; + display:flex; + text-decoration:none; + + img{ + width:48px; + height:48px; + } + & > div{ + display:flex; + flex-direction:column; + flex-grow:1; + flex-basis:0; + margin-left:8px; + overflow:hidden; + + span{ + text-align:left; + &:first-child{ + font-size:14px; + font-weight:700; + color: rgba(0, 0, 0, 1); + } + + &:nth-child(n +1){ + font-size:12px; + color: rgba(0, 0, 0, 0.6); + } + + } + } + } + + button{ + possition:absolute; + right: 12px; + top:0; + background:transparent; + border:none; + outline: none; + + + img{ + width:20px; + height:20px; + padding-bottom: 20px; + + } + } + `; + + const Discription = styled.div` + padding: 0 16px; + overflow:hidden; + color: rgba(0, 0, 0, 0.9); + font-size:14px; + text-align:left; + `; + + const SharedImg= styled.div` + margin-top:8px; + width:100%; + display:block; + position:relative; + background-color:#f9fafb; + + img{ + object-fit:contain; + width: auto; + height:400px; + + + } + `; + + const SocialCount = styled.ul` + line-height:1.3; + display:flex; + align-items:flex-start; + overflow:auto; + margin:0 16px; + padding:8px 0; + border-bottom:1px solid #e9e5df; + list-style:none; + Button{ + align-items:center; + background-color:transparent; + border: none; + } + + li{ + margin-right:5px; + font-size:12px; + button{ + display:flex; + } + img{ + width:25px; + } + } + `; + + const SocialAction = styled.div` + align-items:center; + display:flex; + justify-content:space-between; + margin:0; + min-height:40px; + padding:4px 70px 8px 9px; + + + button{ + display:inline-flex; + align-items:center; + color: #0a66c2; + width:50px; + height: 50px; + background-color:transparent; + border:none; + + img{ + width:50px; + height:50px; + } + + @media(min-width:768px){ + span{ + margin-left:8px; + } + a{ + margin-left:8px; + } + } + } + + `; + const Content = styled.div` + text-align:center; + &>img { + width:30px; + } + `; + + const mapStateToProps = (state) =>{ + return{ + loading: state.articleState.loading, + user: state.userState.user, + articles:state.articleState.articles, + }; + }; + + const mapDispatchToProps = (dispatch) =>({ + getArticles: () => dispatch(getArticlesAPI()), + }); + + +export default connect(mapStateToProps, mapDispatchToProps)(Main); + diff --git a/src/components/PostModal.js b/src/components/PostModal.js new file mode 100644 index 00000000..7dbea8e0 --- /dev/null +++ b/src/components/PostModal.js @@ -0,0 +1,283 @@ +import styled from "styled-components"; +import {useState} from "react"; +import ReactPlayer from "react-player"; +import {connect } from "react-redux"; +import firebase from "firebase"; +import {postArticleAPI} from "../actions"; + + +const PostModal =(props) =>{ + const [editorText, setEditorText] = useState(""); + const [shareImage, setShareImage] = useState(""); + const [videoLink,setVideoLink] = useState(""); + const [assetArea, setAssetArea] = useState(""); + + const handleChange= (e) =>{ + const image = e.target.files[0]; + if(image === '' || image === undefined){ + alert('not an image ,the file is a ${typeof image}') + return; + } + setShareImage(image); + }; + const switchAssetArea= (area) =>{ + setShareImage(""); + setVideoLink(""); + setAssetArea(area); + }; + + const postArticle=(e) =>{ + e.preventDefault(); + if(e.target !== e.currentTarget){ + return; + } + const payload ={ + image:shareImage, + video:videoLink, + user: props.user, + description:editorText, + timestamp: firebase.firestore.Timestamp.now(), + + }; + props.postArticle(payload); + reset(e); + }; + + const reset= (e) =>{ + setEditorText(""); + setShareImage(""); + setVideoLink(""); + setAssetArea(""); + props.handleClick(e); + }; + + return( + <> + { props.showModal === 'open' && ( + + +
+

Create post

+ +
+ + + { props.user.photoURL ? ( + + ):( + + )} + {props.user.displayName} + + + + { assetArea === "image" ? ( + + +

+ {shareImage && } +
+ ):( + + assetArea === "media" && ( + + <> setVideoLink(e.target.value)} /> + {videoLink &&( + )} + )) + } + +
+
+ + switchAssetArea("image")} > + + + switchAssetArea("media")}> + + + + + + + Anyone + + + postArticle(event)}>Post + +
+
+ )} + ); +}; + +const Container = styled.div` + position :fixed ; + top:0; + left:0; + right:0; + bottom:0; + z-index:9999; + color: black; + background-color: rgba(0,0,0,0.8); + animation: fadeIn 0.3s; + + +`; +const Content = styled.div` + width:100%; + max-width:552px; + background-color:white; + max-height: 90%; + overflow:initial; + border-radius:5px; + position:relative; + flex-direction:column; + top:32px; + margin:0 auto; + overflow-y: scroll; +`; + + +const Header = styled.div` + display:block; + padding:16px 20px; + border-bottom:1px solid rgba(0,0,0,0.15); + font-size:16px; + line-height:1.5; + color:rgba(0,0,0,0.6); + font-weight:400; + display:flex; + justify-content:space-between; + align-items:center; + button{ + height:40px; + width:40px; + min-height:auto; + color:rgba(0,0,0,0.15); + svg, + img { + pointer-events:none; + } + } +`; + +const ShareContent = styled.div` +display:flex; +flex-direction: column; +flex-grow:1; +overflow-y:auto; +vartical-align:baseline; +padding:8px 12px; +background:transparent; +`; +const UserInfo = styled.div` +display: flex; +align-items:center; +padding:12px 24px; +svg,img{ + width:48px; + height:48px; + background-clip:content-box; + border:2px solid transparent; + border-radius:50%; +} +span{ + font-weight:600; + font-size:16px; + line-height:1.5; + margin-left:5px; +} +`; + +const ShareCreation = styled.div` + display:flex; + justify-content:space-between; + padding:12px 24px 12px 16px; +`; +const AssetButton = styled.div` + display: flex; + align-items:center; + height:40px; + min-width:auto; + color: rgba(0, 0, 0,0.5); + +`; + +const AttachAssets=styled.div` + align-items: center; + display:flex; + padding-right:8px; + ${AssetButton} { + width:50px; + } + +`; +const ShareComment=styled.div` + padding-left:8px; + margin-right:auto; + border-left:1px solid rgba(0,0,0,0.15); + + ${AssetButton} { + margin-right: 5px; +} +`; +const PostButton=styled.div` + min-width:60px; + border:2px solid rgba(0,0,0,0.15); + border-radius:20px; + padding-left:16px; + padding-right: 16px; + background: ${(props)=> (props.disabled ? "rgba(0,0,0,0.8)" :"#0a66c2")}; + text-align:center; + padding-top:10px; + color: ${(props)=> (props.disabled ? "rgba(1,1,0,0.4)" :"White")}; + &:hover{ + background: ${(props)=> (props.disabled ? "rgba(0,0,0,0.08)" :"#004182")}; + } +`; + +const Editor = styled.div` +padding:12px 24px; +textarea{ + width:100%; + min-height:100px; + resize:nome; +} +input{ + width:100%; + height:35px; + font-size:16px; + margin-bottom:20px; +} +`; + +const UploadImage = styled.div` +text-align:center; +img{ + width:auto; + height:250px; +} +`; +const mapStateToProps= (state) =>{ + return{ + user: state.userState.user, + } +}; + +const mapDispatchToProps=(dispatch) =>({ + postArticle: (payload) => dispatch(postArticleAPI(payload)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(PostModal); \ No newline at end of file diff --git a/src/index.js b/src/index.js index 1bc4857e..4966d3df 100644 --- a/src/index.js +++ b/src/index.js @@ -2,10 +2,14 @@ import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; +import{ Provider} from "react-redux"; +import store from "./store"; ReactDOM.render( - + + + , document.getElementById("root") ); diff --git a/src/reducers/articleReducer.js b/src/reducers/articleReducer.js new file mode 100644 index 00000000..29258f7c --- /dev/null +++ b/src/reducers/articleReducer.js @@ -0,0 +1,25 @@ +import { SET_LOADING_STATUS ,GET_ARTICLES} from "../actions/actionType"; + + +export const initState ={ + articles:[], + loading:false, +}; + +const articleReducer=(state = initState, action) =>{ + switch (action.type){ + case GET_ARTICLES: + return{ + ...state, + articles:action.payload, + }; + case SET_LOADING_STATUS: + return{ + ...state, + loading: action.status, + }; + default: + return state; + } +}; +export default articleReducer; \ No newline at end of file diff --git a/src/reducers/index.js b/src/reducers/index.js new file mode 100644 index 00000000..fe9e7df1 --- /dev/null +++ b/src/reducers/index.js @@ -0,0 +1,12 @@ +import{combineReducers} from "redux"; +import userReducer from "./userReducer"; +import articleReducer from "./articleReducer"; + + +const rootReducer = combineReducers({ + + userState: userReducer, + articleState: articleReducer, +}); + +export default rootReducer; \ No newline at end of file diff --git a/src/reducers/userReducer.js b/src/reducers/userReducer.js new file mode 100644 index 00000000..0d83eac2 --- /dev/null +++ b/src/reducers/userReducer.js @@ -0,0 +1,20 @@ +import {SET_USER} from '../actions/actionType'; + + +const INITIAL_STATE ={ + user: null, +}; + +const userReducer = (state= INITIAL_STATE,action)=>{ + switch (action.type){ + case SET_USER: + return{ + ...state, + user: action.user, + }; + default: + return state; + } +} + +export default userReducer; \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 00000000..688838c0 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,9 @@ +import {createStore, applyMiddleware} from 'redux'; +import rootReducer from "../reducers"; +import thunkMiddleware from "redux-thunk"; + + +const store =createStore(rootReducer,applyMiddleware(thunkMiddleware)); + + +export default store; \ No newline at end of file