This commit is contained in:
Senthil Chandran 2018-10-17 11:22:41 -07:00
Родитель fdeaa33157
Коммит 451f48ebb0
47 изменённых файлов: 12399 добавлений и 340 удалений

76
.bootstraprc Normal file
Просмотреть файл

@ -0,0 +1,76 @@
### Bootstrap styles
bootstrapVersion: 3
styleLoaders:
- style
- css
- sass
preBootstrapCustomizations: ./src/styles/_variables.scss
styles:
# Mixins
mixins: true
# Reset and dependencies
normalize: true
print: true
glyphicons: true
# Core CSS
scaffolding: true
type: true
code: true
grid: true
tables: true
forms: true
buttons: true
# Components
component-animations: true
dropdowns: true
button-groups: true
input-groups: true
navs: true
navbar: true
breadcrumbs: true
pagination: true
pager: true
labels: true
badges: true
jumbotron: true
thumbnails: true
alerts: true
progress-bars: true
media: true
list-group: true
panels: true
wells: true
responsive-embed: true
close: true
# Components w/ JavaScript
modals: true
tooltip: true
popovers: true
carousel: true
# Utility classes
utilities: true
responsive-utilities: true
### Bootstrap scripts
scripts:
transition: true
alert: true
button: true
carousel: true
collapse: true
dropdown: true
modal: true
tooltip: true
popover: true
scrollspy: true
tab: true
affix: true

334
.gitignore поставляемый
Просмотреть файл

@ -1,330 +1,4 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
node_modules/*.*
public/*.*
src/**/*.js
src/**/*.js.map

3
.vs/ProjectSettings.json Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}

Двоичные данные
.vs/slnx.sqlite Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -1,14 +1,42 @@
# TypeScript React Redux Bootstrap Sass Webpack Starter
# Contributing
This repository
* is a single page application with React and Redux using TypeScript
* uses Bootstrap framework and Sass as preprocessor
* testing with Jest and Enzyme
* uses webpack as module bundler
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
# Install python
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
node-sass uses python, so please install python using
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
```shell
npm --add-python-to-path='true' --debug install --global windows-build-tools
```
# Running application locally
```shell
npm install
```
NOTE: Without performing Install python step, npm install would fail
```shell
npm run build:dev
```
```shell
npm run start
```
NOTE: Will start the application in localhost:3001 using webpack dev server
# Testing the project
```shell
npm run test
```
Updating snapshot
```shell
npm run test:update-snapshot
```

96
configs/webpack.dev.js Normal file
Просмотреть файл

@ -0,0 +1,96 @@
var webpack = require("webpack");
var HtmlWebpackPlugin = require("html-webpack-plugin");
var path = require("path");
module.exports = {
entry: "./src/index.tsx",
output: {
filename: "bundle-dev.js",
path: path.resolve(__dirname, "..", "public"),
publicPath: "/"
},
resolve: {
extensions: [".tsx", ".ts", ".js", ".json", ".html"],
modules: ["node_modules", path.resolve(__dirname, "..", "src")]
},
resolveLoader: {
modules: ["node_modules"]
},
devServer: {
inline: true,
contentBase: "public/",
port: 3001,
proxy: [
{
path: "/api/*",
target: "http://localhost:3001/"
}
],
historyApiFallback: true
},
devtool: "source-map",
module: {
rules: [
{
test: /\.ts(x?)$/,
loader: "ts-loader",
exclude: /node_modules/
},
{
enforce: "pre",
test: /\.js$/,
loader: "source-map-loader"
},
{
test: /\.css$/,
exclude: /\.useable\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.useable\.css$/,
loader: "style-loader/useable!css"
},
{
test: /\.css$/,
loader: 'style-loader!css-loader'
},
{
test: /\.scss$/,
loader: 'style-loader!css-loader!sass-loader'
},
{
test: /bootstrap\/js\//,
loader: 'imports-loader?jQuery=jquery'
},
{
test: /bootstrap-sass[\/\\]assets[\/\\]javascripts[\/\\]/,
loader: 'imports-loader?jQuery=jquery'
},
{
test: /\.html$/,
exclude: /node_modules/,
loader: 'html-loader'
},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000&mimetype=application/font-woff'
},
{
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "src/index.html",
hash: true
}),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
'window.jquery': 'jquery'
})
]
};

11034
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

83
package.json Normal file
Просмотреть файл

@ -0,0 +1,83 @@
{
"name": "react-redux",
"version": "1.0.0",
"description": "React Redux Application",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --open --config configs/webpack.dev.js",
"build:dev": "webpack --config configs/webpack.dev.js -d",
"build:prod": "webpack --config configs/webpack.prod.js",
"test": "jest",
"test:update-snapshot": "jest --updateSnapshot"
},
"author": "senthilc",
"license": "",
"dependencies": {
"@types/prop-types": "15.5.4",
"@types/react": "16.0.21",
"@types/react-dom": "16.0.2",
"@types/react-redux": "6.0.5",
"@types/react-router-dom": "4.2.1",
"@types/redux": "3.6.0",
"bootstrap": "3.3.7",
"jquery": "3.3.1",
"prop-types": "15.6.2",
"react": "16.0.0",
"react-dom": "16.0.0",
"react-redux": "5.0.7",
"react-router-dom": "4.2.2",
"redux": "4.0.0",
"redux-thunk": "2.3.0"
},
"devDependencies": {
"@types/enzyme": "^3.1.12",
"@types/enzyme-adapter-react-16": "^1.0.2",
"@types/jest": "^21.1.7",
"bootstrap-loader": "^3.0.0",
"bootstrap-sass": "^3.3.7",
"css-loader": "^0.28.7",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"expose-loader": "^0.7.5",
"file-loader": "^1.1.11",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^2.30.1",
"identity-obj-proxy": "^3.0.0",
"imports-loader": "^0.8.0",
"jest": "^21.2.1",
"node-sass": "^4.7.2",
"react-test-renderer": "^16.1.1",
"resolve-url-loader": "^2.3.0",
"sass-loader": "^6.0.6",
"sass-resources-loader": "^1.3.1",
"source-map-loader": "^0.2.3",
"style-loader": "^0.19.0",
"ts-jest": "^21.2.3",
"ts-loader": "^3.1.1",
"typescript": "^3.0.1",
"typings": "^2.1.1",
"url-loader": "^1.0.1",
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.4"
},
"jest": {
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json"
],
"moduleDirectories": [
"node_modules",
"src"
],
"moduleNameMapper": {
"^.+\\.(css|less|scss)$": "identity-obj-proxy"
}
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Footer component tests Check Footer Component Render 1`] = `
<footer>
<div
className="container-fluid footer"
>
<div
className="row"
>
<div
className="col-md-12"
>
<ul
className="pull-right list-unstyled"
>
<li>
<a
href=""
>
Contact us
</a>
</li>
<li>
© Microsoft
2018
</li>
</ul>
</div>
</div>
</div>
</footer>
`;

Просмотреть файл

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Main component tests Check Main Component Render 1`] = `null`;

Просмотреть файл

@ -0,0 +1,111 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Navigation component tests Check Navigation Component Render 1`] = `
<div
className="navigation"
>
<div
className="navigation-logo"
>
<nav
className="navbar navbar-default"
>
<div
className="container-fluid"
>
<div
className="navbar-header"
>
<button
aria-expanded="false"
className="navbar-toggle collapsed"
data-target="#nav-logo"
data-toggle="collapse"
type="button"
>
<span
className="sr-only"
>
Toggle navigation
</span>
<span
className="icon-bar"
/>
<span
className="icon-bar"
/>
<span
className="icon-bar"
/>
</button>
<a
className="navbar-brand"
href="/"
>
<img
alt="Brand"
src=""
/>
</a>
</div>
<div
className="collapse navbar-collapse"
id="nav-logo"
>
<ul
className="nav navbar-nav navbar-right"
>
<li
className="hidden-md hidden-lg"
>
<a
href="/"
onClick={[Function]}
>
Brand
</a>
</li>
<li>
<a
href="#"
onClick={[Function]}
>
Sign in
</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
<div
className="navigation"
>
<nav
className="navbar navbar-default"
>
<div
className="container-fluid"
>
<div
className="navbar-header"
>
<a
className="navbar-brand hidden-xs hidden-sm"
href="/"
onClick={[Function]}
>
Brand
</a>
</div>
<div
className="collapse navbar-collapse"
id="nav"
>
</div>
</div>
</nav>
</div>
</div>
`;

Просмотреть файл

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NotFound component tests Check NotFound Component Render 1`] = `
<div
className="container-fluid"
>
<h1>
404 Page Not Found
</h1>
</div>
`;

Просмотреть файл

@ -0,0 +1,154 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Routes component tests Check Routes Component Render 1`] = `
<div
className="wrapper"
>
<div
className="navigation"
>
<div
className="navigation-logo"
>
<nav
className="navbar navbar-default"
>
<div
className="container-fluid"
>
<div
className="navbar-header"
>
<button
aria-expanded="false"
className="navbar-toggle collapsed"
data-target="#nav-logo"
data-toggle="collapse"
type="button"
>
<span
className="sr-only"
>
Toggle navigation
</span>
<span
className="icon-bar"
/>
<span
className="icon-bar"
/>
<span
className="icon-bar"
/>
</button>
<a
className="navbar-brand"
href="/"
>
<img
alt="Brand"
src=""
/>
</a>
</div>
<div
className="collapse navbar-collapse"
id="nav-logo"
>
<ul
className="nav navbar-nav navbar-right"
>
<li
className="hidden-md hidden-lg"
>
<a
href="/"
onClick={[Function]}
>
Brand
</a>
</li>
<li>
<a
href="#"
onClick={[Function]}
>
Sign in
</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
<div
className="navigation"
>
<nav
className="navbar navbar-default"
>
<div
className="container-fluid"
>
<div
className="navbar-header"
>
<a
className="navbar-brand hidden-xs hidden-sm"
href="/"
onClick={[Function]}
>
Brand
</a>
</div>
<div
className="collapse navbar-collapse"
id="nav"
>
</div>
</div>
</nav>
</div>
</div>
<div
className="container-fluid"
>
<h1>
404 Page Not Found
</h1>
</div>
<div
className="push"
/>
<footer>
<div
className="container-fluid footer"
>
<div
className="row"
>
<div
className="col-md-12"
>
<ul
className="pull-right list-unstyled"
>
<li>
<a
href=""
>
Contact us
</a>
</li>
<li>
© Microsoft
2018
</li>
</ul>
</div>
</div>
</div>
</footer>
</div>
`;

Просмотреть файл

@ -0,0 +1,16 @@
import React from "react";
import renderer from "react-test-renderer";
import { Footer } from "components";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { MemoryRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import { users } from "../../redux/modules/users/users"
describe("Footer component tests", () => {
it("Check Footer Component Render", () => {
const store = createStore(users, applyMiddleware(thunk));
const component = renderer.create(<Footer />).toJSON();
expect(component).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,17 @@
import React from "react";
import renderer from "react-test-renderer";
import { MemoryRouter as Router } from "react-router-dom";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { Provider } from "react-redux";
import { users } from "../../redux/modules/users/users"
import { Main } from "components";
import { Home } from "modules";
describe("Main component tests", () => {
it("Check Main Component Render", () => {
const store = createStore(users, applyMiddleware(thunk));
const component = renderer.create(<Provider store={store}><Router><Main path="/home" component={Home} checkAuthentication={false} /></Router></Provider>).toJSON();
expect(component).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,16 @@
import React from "react";
import renderer from "react-test-renderer";
import { Navigation } from "components";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { MemoryRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import { users } from "../../redux/modules/users/users"
describe("Navigation component tests", () => {
it("Check Navigation Component Render", () => {
const store = createStore(users, applyMiddleware(thunk));
const component = renderer.create(<Provider store={store}><Router><Navigation history={[]} /></Router></Provider>).toJSON();
expect(component).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,10 @@
import React from "react";
import renderer from "react-test-renderer";
import { NotFound } from "components";
describe("NotFound component tests", () => {
it("Check NotFound Component Render", () => {
const component = renderer.create(<NotFound />).toJSON();
expect(component).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,15 @@
import React from "react";
import renderer from "react-test-renderer";
import { Routes } from "components";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import { users } from "../../redux/modules/users/users"
describe("Routes component tests", () => {
it("Check Routes Component Render", () => {
const store = createStore(users, applyMiddleware(thunk));
const component = renderer.create(<Provider store={store}><Routes /></Provider>).toJSON();
expect(component).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Home component tests Check Home Component Render 1`] = `
<div
className="container-fluid home"
>
<div
className="row"
>
<div
className="hero-container"
>
<img
alt=""
className="hero-img"
src=""
/>
<h1
className="hero-caption one"
>
Brand
</h1>
<button
className="btn btn-white hero-sign-in"
onClick={[Function]}
>
Sign in
<i
className="glyphicon glyphicon-menu-right"
/>
</button>
</div>
</div>
</div>
`;

Просмотреть файл

@ -0,0 +1,15 @@
import React from "react";
import renderer from "react-test-renderer";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { Provider } from "react-redux";
import { users } from "../../redux/modules/users/users"
import { Home } from "modules";
describe("Home component tests", () => {
it("Check Home Component Render", () => {
const store = createStore(users, applyMiddleware(thunk));
const component = renderer.create(<Provider store={store}><Home history={[]} /></Provider>).toJSON();
expect(component).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,12 @@
export const authenticate = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve({
name: "Senthil Chandran",
uid: "1"
}), 2000)
});
}
export const checkIfAuthenticated = (store: any) => {
return store.getState().isAuthed;
}

Просмотреть файл

@ -0,0 +1,20 @@
@import "../../styles/variables";
.footer {
padding-top: 60px;
ul {
font-size: 11px;
line-height: 16px;
margin: 3px 0;
li {
padding: 0 24px 4px 0;
display: inline-block;
a {
color: gray;
}
a:hover {
color: blue;
text-decoration: underline;
}
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
import "./footer.scss";
import { Resources } from "../../resources";
import * as React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
export class Footer extends React.Component {
render() {
return (
<footer>
<div className="container-fluid footer">
<div className="row">
<div className="col-md-12">
<ul className="pull-right list-unstyled">
<li>
<a href="">{Resources.Footer.ContactUs}</a>
</li>
<li>
© Microsoft {new Date().getFullYear()}
</li>
</ul>
</div>
</div>
</div>
</footer>
)
}
}

6
src/components/index.ts Normal file
Просмотреть файл

@ -0,0 +1,6 @@
export { Main } from "./main/main";
export { Routes } from "./routes/routes";
export { Navigation } from "./navigation/navigation";
export { Footer } from "./footer/footer";
export { authenticate, checkIfAuthenticated } from "./authentication/authentication";
export { NotFound } from "./notfound/notfound";

Просмотреть файл

@ -0,0 +1,50 @@
@import "../../styles/variables";
html {
position: relative;
min-height: 100%;
}
body {
margin: 0 0 $footer-height;
}
.wrapper {
min-height: 100%;
height: auto !important;
}
.push {
height: $push-height;
}
footer {
position: absolute;
left: 0;
min-height: $footer-height;
width: 100%;
background-color: #E3E3E3;
color: #5E5E5E;
}
@media screen and (min-width: 992px) {
footer {
bottom: 0;
}
}
.btn-white {
background-color: white;
color: black;
border-color: white;
&:hover {
background-color: #E3E3E3;
color: black;
border-color: white;
}
&:active {
background-color: #ACACAC;
color: black;
border-color: white;
}
}

Просмотреть файл

@ -0,0 +1,40 @@
import "./main.scss";
import * as React from "react";
import { BrowserRouter as Router, Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Navigation, Footer } from "components";
import * as userActionCreators from "../../redux/modules/users/users";
interface IMainContainerProps {
component: any;
isAuthed: boolean;
path?: string;
exact?: boolean;
checkAuthentication: boolean;
}
const MainContainer: React.StatelessComponent<IMainContainerProps> = (props) => {
const { component: Component, ...rest } = props;
return <Route {...rest} render={matchProps =>
(props.checkAuthentication && !props.isAuthed) ?
(
<Redirect to="/" />
) :
(
<div className="wrapper">
<Navigation history={matchProps.history} />
<Component {...matchProps} />
<div className="push"></div>
<Footer />
</div>
)
} />
}
export const Main = connect(
(state: any) => {
return ({ isAuthed: state.isAuthed });
},
(dispatch) => bindActionCreators(userActionCreators, dispatch)
)(MainContainer);

Просмотреть файл

@ -0,0 +1,7 @@
@import "../../styles/variables";
.navigation {
.navbar-default {
background-color: white;
border-color: white;
}
}

Просмотреть файл

@ -0,0 +1,108 @@
import "./navigation.scss";
import { Resources } from "resources"
import * as React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import * as PropTypes from "prop-types";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as userActionCreators from "../../redux/modules/users/users";
interface INavigationComponentState {
}
const initialState: INavigationComponentState = {
};
interface INavigationComponentProps {
history: any;
isAuthed: boolean;
isFetching: boolean;
error: string;
unauthUser: () => void;
fetchAndHandleAuthentication: (history: any) => void;
}
class NavigationComponent extends React.Component<INavigationComponentProps, INavigationComponentState> {
static propTypes = {
isAuthed: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.string.isRequired,
unauthUser: PropTypes.func.isRequired,
fetchAndHandleAuthentication: PropTypes.func.isRequired
}
constructor(props: INavigationComponentProps) {
super(props);
this.state = initialState;
}
handleAuth = () => {
this.props.fetchAndHandleAuthentication(this.props.history);
}
handleUnauth = () => {
this.props.unauthUser();
this.props.history.push("/");
}
render() {
return (
<div className="navigation">
<div className="navigation-logo">
<nav className="navbar navbar-default">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#nav-logo" aria-expanded="false">
<span className="sr-only">{Resources.Navigation.ToggleNavigation}</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<a className="navbar-brand" href="/"><span>Brand</span></a>
</div>
<div className="collapse navbar-collapse" id="nav-logo">
{this.props.isAuthed ? (
<ul className="nav navbar-nav navbar-right">
<li className="hidden-md hidden-lg"><Link to="/">Brand</Link></li>
<li className="hidden-md hidden-lg"><Link to="/dashboard">{Resources.Navigation.Dashboard}</Link></li>
<li><Link to="/settings">{Resources.Navigation.Settings}</Link></li>
<li><a href="#" onClick={this.handleUnauth}>{Resources.Navigation.SignOut}</a></li>
</ul>
) : (
<ul className="nav navbar-nav navbar-right">
<li className="hidden-md hidden-lg"><Link to="/">Brand</Link></li>
{!this.props.isFetching ? (
<li><a href="#" onClick={this.handleAuth}>{Resources.Navigation.SignIn}</a></li>
) : ("")
}
</ul>
)
}
</div>
</div>
</nav>
</div>
<div className="navigation">
<nav className="navbar navbar-default">
<div className="container-fluid">
<div className="navbar-header">
<Link className="navbar-brand hidden-xs hidden-sm" to="/">React-Redux Starer</Link>
</div>
<div className="collapse navbar-collapse" id="nav">
{this.props.isAuthed ? (
<ul className="nav navbar-nav">
<li><Link to="/dashboard">{Resources.Navigation.Dashboard}</Link></li>
</ul>
) : ("")
}
</div>
</div>
</nav>
</div>
</div>
)
}
}
export const Navigation = connect(
(state: any) => {
return ({isAuthed: state.isAuthed, isFetching: state.isFetching, error: state.error });
},
(dispatch) => bindActionCreators(userActionCreators, dispatch)
)(NavigationComponent);

Просмотреть файл

@ -0,0 +1,12 @@
import { Resources } from "resources";
import * as React from "react";
export class NotFound extends React.Component {
render() {
return (
<div className="container-fluid">
<h1>{Resources.NotFound.NotFound}</h1>
</div>
)
}
}

Просмотреть файл

@ -0,0 +1,21 @@
import * as React from "react";
import { BrowserRouter as Router, Route, Switch, Redirect } from "react-router-dom";
import { Main, NotFound } from "components";
import { Home, Dashboard } from "modules";
export class Routes extends React.Component {
render() {
return (
<Router>
<Switch>
<Route exact path="/">
<Redirect to="/home" />
</Route>
<Main path="/home" component={Home} checkAuthentication={false} />
<Main path="/dashboard" component={Dashboard} checkAuthentication={true} />
<Main path="*" component={NotFound} checkAuthentication={false} />
</Switch>
</Router>
)
}
}

10
src/index.html Normal file
Просмотреть файл

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Brand</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

16
src/index.tsx Normal file
Просмотреть файл

@ -0,0 +1,16 @@
import "bootstrap-loader";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import { Routes } from "components";
import { users } from "./redux/modules/users/users"
const store = createStore(users, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<Routes />
</Provider>,
document.getElementById("app")
);

1
src/models/index.ts Normal file
Просмотреть файл

@ -0,0 +1 @@
export { User } from "./user";

5
src/models/user.ts Normal file
Просмотреть файл

@ -0,0 +1,5 @@
export class User {
public uid: string;
public name: string;
public lastUpdated: Date;
}

Просмотреть файл

Просмотреть файл

@ -0,0 +1,13 @@
import "./dashboard.scss";
import * as React from "react";
export class Dashboard extends React.Component {
render() {
return (
<div className="container-fluid dashboard">
<h1>Welcome to Dashboard!</h1>
</div>
)
}
}

Просмотреть файл

@ -0,0 +1,23 @@
@import "../../styles/variables";
.home {
.hero-container {
position: relative;
background-color: blue;
width: auto;
height: 500px;
.hero-sign-in {
position: absolute;
left:5%;
bottom: 50%;
padding-left: 100px;
padding-right: 100px;
margin-left: 7px;
.glyphicon-menu-right {
padding-left: 5px;
}
&.welcome {
color: white;
}
}
}
}

70
src/modules/home/home.tsx Normal file
Просмотреть файл

@ -0,0 +1,70 @@
import "./home.scss";
import { Resources } from "resources";
import * as React from "react";
import * as PropTypes from "prop-types";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as userActionCreators from "../../redux/modules/users/users";
interface IHomeComponentState {
}
const initialState: IHomeComponentState = {
};
interface IHomeComponentProps {
history: any;
isAuthed: boolean;
isFetching: boolean;
error: string;
fetchAndHandleAuthentication: (history: any) => void;
}
class HomeComponent extends React.Component<IHomeComponentProps, IHomeComponentState> {
static propTypes = {
isAuthed: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.string.isRequired,
fetchAndHandleAuthentication: PropTypes.func.isRequired
}
constructor(props: IHomeComponentProps) {
super(props);
this.state = initialState;
}
handleAuth = () => {
this.props.fetchAndHandleAuthentication(this.props.history);
}
render() {
return (
<div className="container-fluid home">
<div className="row">
<div className="hero-container">
{!this.props.isAuthed ? (
<button className="btn btn-white hero-sign-in" onClick={this.handleAuth}>
{Resources.Home.SignIn}
<i className="glyphicon glyphicon-menu-right"></i>
</button>
) : (
<h1 className="hero-sign-in welcome">Welcome to Home!</h1>
)
}
{this.props.isFetching ? (
<button className="btn btn-white hero-sign-in">
{Resources.Home.SigningIn}
</button>
) : ("")
}
</div>
</div>
</div>
)
}
}
export const Home = connect(
(state: any) => {
return ({ isFetching: state.isFetching, error: state.error, isAuthed: state.isAuthed });
},
(dispatch) => bindActionCreators(userActionCreators, dispatch)
)(HomeComponent);

2
src/modules/index.ts Normal file
Просмотреть файл

@ -0,0 +1,2 @@
export { Home } from "./home/home";
export { Dashboard } from "./dashboard/dashboard";

Просмотреть файл

@ -0,0 +1,124 @@
import { User } from "models";
import { authenticate, checkIfAuthenticated } from "components";
const AUTH_USER = 'AUTH_USER'
const UNAUTH_USER = 'UNAUTH_USER'
const FETCHING_USER = 'FETCHING_USER'
const FETCHING_USER_FAILURE = 'FETCHING_USER_FAILURE'
const FETCHING_USER_SUCCESS = 'FETCHING_USER_SUCCESS'
const authUser = (uid: string) => {
return {
type: AUTH_USER,
uid,
}
}
export const unauthUser = () => {
return {
type: UNAUTH_USER,
}
}
const fetchingUser = () => {
return {
type: FETCHING_USER,
}
}
const fetchingUserFailure = (error: string) => {
return {
type: FETCHING_USER_FAILURE,
error: 'Error fetching user.',
}
}
const fetchingUserSuccess = (uid: string, user: User, timestamp: Date) => {
return {
type: FETCHING_USER_SUCCESS,
uid,
user,
timestamp,
}
}
export const fetchAndHandleAuthentication = (history: any) => {
return (dispatch: any) => {
dispatch(fetchingUser());
authenticate().then((user: any) => {
dispatch(fetchingUserSuccess(user.uid, user, new Date()));
dispatch(authUser(user.uid));
history.push("/dashboard");
}).catch((error) => dispatch(fetchingUserFailure(error)));
};
}
const initialUserState: User = {
uid: "",
name: "",
lastUpdated: new Date()
}
export const user = (state = initialUserState, action: any) => {
switch (action.type) {
case FETCHING_USER_SUCCESS :
return {
...state,
uid: action.uid,
name: action.user.name,
lastUpdated: action.timestamp
}
default :
return state
}
}
const initialState = {
isFetching: false,
error: "",
isAuthed: false,
authedId: "",
}
export const users = (state = initialState, action: any) => {
switch (action.type) {
case AUTH_USER :
return {
...state,
isAuthed: true,
authedId: action.uid,
}
case UNAUTH_USER :
return {
...state,
isAuthed: false,
authedId: "",
}
case FETCHING_USER:
return {
...state,
isFetching: true,
}
case FETCHING_USER_FAILURE:
return {
...state,
isFetching: false,
error: action.error,
}
case FETCHING_USER_SUCCESS:
return action.user === null
? {
...state,
isFetching: false,
error: "",
}
: {
...state,
isFetching: false,
error: "",
[action.uid]: user(state[action.uid], action),
}
default :
return state
}
}

5
src/resources/footer.ts Normal file
Просмотреть файл

@ -0,0 +1,5 @@
export var FooterResx = {
ContactUs: "Contact us",
}
export type IFooterResx = typeof FooterResx;

6
src/resources/home.ts Normal file
Просмотреть файл

@ -0,0 +1,6 @@
export var HomeResx = {
SignIn: "Sign in",
SigningIn: "Signing in...",
}
export type IHomeResx = typeof HomeResx;

13
src/resources/index.ts Normal file
Просмотреть файл

@ -0,0 +1,13 @@
import { HomeResx, IHomeResx } from "./home";
import { NavigationResx, INavigationResx } from "./navigation";
import { FooterResx, IFooterResx } from "./footer";
import { NotFoundResx, INotFoundResx } from "./notfound";
export var Resources = {
Home: HomeResx,
Navigation: NavigationResx,
Footer: FooterResx,
NotFound: NotFoundResx,
}
export type IResources = typeof Resources;

Просмотреть файл

@ -0,0 +1,10 @@
export var NavigationResx = {
Home: "Home",
Dashboard: "Dashboard",
SignIn: "Sign in",
SignOut: "Sign out",
ToggleNavigation: "Toggle navigation",
Settings: "Settings"
}
export type INavigationResx = typeof NavigationResx;

Просмотреть файл

@ -0,0 +1,5 @@
export var NotFoundResx = {
NotFound: "404 Page Not Found"
}
export type INotFoundResx = typeof NotFoundResx;

Просмотреть файл

@ -0,0 +1,2 @@
$footer-height: 100px;
$push-height: 50px;

19
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"jsx": "react",
"sourceMap": true,
"removeComments": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"noImplicitAny": false,
"lib": ["es5", "es2015", "dom", "scripthost"]
},
"include": [
"./src/**/*"
],
"exclude": [
"node_modules"
]
}