diff --git a/src/player/sagas.js b/src/player/sagas.js
deleted file mode 100644
index e69de29..0000000
diff --git a/src/reducers/index.js b/src/reducers/index.js
index a131560..937460c 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -5,13 +5,14 @@ import modal from "../modal/reducer"
 import client from "../client/reducer"
 import snackbar from "../snackbar/reducer"
 import player from "../player/reducer"
-
+import upload from "../upload/reducer"
 export default combineReducers({
 	routing: routerReducer,
 	client,
 	modal,
   signup,
 	snackbar,
-	player
+	player,
+  upload
 })
 
diff --git a/src/sagas/index.js b/src/sagas/index.js
index a0b36bc..ef32e6e 100644
--- a/src/sagas/index.js
+++ b/src/sagas/index.js
@@ -2,12 +2,13 @@ import { all } from "redux-saga/effects"
 import ModalSaga from "../modal/sagas"
 import SignupSaga from "../signup/sagas"
 import SnackbarSaga from "../snackbar/sagas"
+import UploaderSaga from "../upload/sagas"
 
 export default function* rootSagas() {
-	
 	yield all([
 		ModalSaga(),
 		SignupSaga(),
 		SnackbarSaga(),
+    UploaderSaga()
 	])
 }
diff --git a/src/upload/actions.js b/src/upload/actions.js
new file mode 100644
index 0000000..ee42206
--- /dev/null
+++ b/src/upload/actions.js
@@ -0,0 +1,14 @@
+import { UPLOAD_REQUEST, UPLOAD_RESET } from "./constants"
+
+export const uploadRequest = ({file}) => {
+  return {
+    type: UPLOAD_REQUEST,
+    file
+  }
+}
+
+export const uploadReset = () => {
+  return {
+    type: UPLOAD_RESET,
+  }
+}
diff --git a/src/upload/constants.js b/src/upload/constants.js
new file mode 100644
index 0000000..66174cd
--- /dev/null
+++ b/src/upload/constants.js
@@ -0,0 +1,5 @@
+export const UPLOAD_REQUEST = "UPLOAD_REQUEST"
+export const UPLOAD_PROGRESS = "UPLOAD_PROGRESS"
+export const UPLOAD_SUCCESS = "UPLOAD_SUCCESS"
+export const UPLOAD_ERROR = "UPLOAD_ERROR"
+export const UPLOAD_RESET = "UPLOAD_RESET"
diff --git a/src/upload/createFileUploadChannel.js b/src/upload/createFileUploadChannel.js
new file mode 100644
index 0000000..4f5c606
--- /dev/null
+++ b/src/upload/createFileUploadChannel.js
@@ -0,0 +1,47 @@
+/* refs
+https://decembersoft.com/posts/file-upload-progress-with-redux-saga/
+ */
+
+import { buffers, eventChannel, END } from "redux-saga"
+
+const createUploadFileChannel = (endpoint, file) => {
+  return eventChannel(emitter => {
+    const xhr = new XMLHttpRequest()
+    const onProgress = (e) => {
+      if (e.lengthComputable) {
+        const progress = e.loaded / e.total
+        emitter({progress})
+      }
+    }
+    const onFailure = (e) => {
+      emitter({err: new Error("Upload failed")})
+      emitter(END)
+    }
+    xhr.upload.addEventListener("progress", onProgress)
+    xhr.upload.addEventListener("error", onFailure)
+    xhr.upload.addEventListener("abort", onFailure)
+    xhr.onreadystatechange = () => {
+      const {readyState, status} = xhr
+      if (readyState === 4) {
+        if (status === 200) {
+          emitter({success: true})
+          emitter(END)
+        }
+        else {
+          onFailure(null)
+        }
+      }
+    }
+    xhr.open("POST", endpoint, true)
+    xhr.send(file)
+    return () => {
+      xhr.upload.removeEventListener("progress", onProgress)
+      xhr.upload.removeEventListener("error", onFailure)
+      xhr.upload.removeEventListener("abort", onFailure)
+      xhr.onreadystatechange = null
+      xhr.abort()
+    }
+  }, buffers.sliding(2))
+}
+
+export default createUploadFileChannel
diff --git a/src/client/sagas.js b/src/upload/index.js
similarity index 100%
rename from src/client/sagas.js
rename to src/upload/index.js
diff --git a/src/upload/reducer.js b/src/upload/reducer.js
new file mode 100644
index 0000000..1d67f42
--- /dev/null
+++ b/src/upload/reducer.js
@@ -0,0 +1,73 @@
+import { UPLOAD_RESET, UPLOAD_REQUEST, UPLOAD_SUCCESS, UPLOAD_ERROR, UPLOAD_PROGRESS } from "./constants"
+
+const initialState = {
+  file: null,
+  requesting: false,
+  successful: false,
+  progress: 0,
+  messages: [],
+  errors: []
+}
+
+const reducer = (state = initialState, action) => {
+  switch (action.type) {
+    case UPLOAD_RESET:
+      return {
+        file: null,
+        requesting: false,
+        successful: false,
+        progress: 0,
+        messages: [],
+        errors: []
+      }
+    case UPLOAD_REQUEST:
+      return {
+        file: null,
+        requesting: true,
+        successful: false,
+        progress: 0,
+        messages: [{body: "Uploading...", time: new Date()}],
+        errors: []
+      }
+    
+    case UPLOAD_SUCCESS:
+      return {
+        file: action.file,
+        requesting: false,
+        successful: true,
+        progress: 100,
+        errors: [],
+        messages: [{
+          body: `Successfully save file.`,
+          time: new Date()
+        }]
+      }
+    
+    case UPLOAD_ERROR:
+      return {
+        file: state.file,
+        requesting: false,
+        successful: false,
+        progress: null,
+        errors: state.errors.concat([{
+          body: action.error.toString(),
+          time: new Date()
+        }]),
+        messages: []
+      }
+    
+    case UPLOAD_PROGRESS:
+      return {
+        file: action.file,
+        requesting: false,
+        successful: false,
+        progress: action.progress,
+        errors: [],
+        messages: []
+      }
+    default:
+      return state
+  }
+}
+
+export default reducer
diff --git a/src/upload/sagas.js b/src/upload/sagas.js
new file mode 100644
index 0000000..9fd959a
--- /dev/null
+++ b/src/upload/sagas.js
@@ -0,0 +1,37 @@
+import { call, put, take, takeEvery } from "redux-saga/effects"
+import { UPLOAD_REQUEST, UPLOAD_RESET, UPLOAD_PROGRESS, UPLOAD_SUCCESS, UPLOAD_ERROR} from "./constants"
+import {snackbarRequest } from "../snackbar/actions"
+import { PREVIEW_SUCCESS} from "../snackbar/constants"
+import createFileUploadChannel from "./createFileUploadChannel"
+
+// Upload the specified file
+function* uploadFileFlow(file) {
+  const channel = yield call(createFileUploadChannel, "/some/path", file)
+  while (true) {
+    const {progress = 0, error, success} = yield take(channel)
+    if (error) {
+      yield put({type: UPLOAD_ERROR, error})
+      return
+    }
+    if (success) {
+      yield put({type: UPLOAD_SUCCESS, file})
+      return
+    }
+  
+    // show snackbar notification
+    yield put(snackbarRequest({snackbarType: PREVIEW_SUCCESS, snackbarOpen: true}))
+    yield put({type: UPLOAD_PROGRESS, file, progress})
+  }
+}
+
+// Watch for an upload request and then
+// defer to another saga to perform the actual upload
+
+function* uploadFileWatcher() {
+  yield takeEvery(UPLOAD_REQUEST, function* (action) {
+    const file = action.file
+    yield call(uploadFileFlow, file)
+  })
+}
+
+export default uploadFileWatcher