Adding project files.
This commit is contained in:
Родитель
ac4b1ad7bd
Коммит
1a6c02e616
|
@ -1,7 +1,293 @@
|
|||
## 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
|
||||
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# dependencies
|
||||
# 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 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# 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
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# TODO: 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
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# 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
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# React and node dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
OrdersCosmosTrigger function code
|
||||
* Trigger: Azure Cosmos DB on newOrders into the orders collection.
|
||||
* Output: Azure Storage Queue - orderqueue
|
||||
|
||||
Triggers whenever a document in the orders collection is inserted or updated.
|
||||
By checking processed on the document, we only send unprocessed orders to the queue,
|
||||
so when the next Function updates the processed value to true, it won't be requeued.
|
||||
*/
|
||||
module.exports = function (context, newOrders) {
|
||||
for(var i=0; i < newOrders.length; i++) {
|
||||
var newOrder = newOrders[i];
|
||||
var orderId = newOrder['$v']['id']['$v'];
|
||||
var userId = newOrder['$v']['userId']['$v'];
|
||||
var sendNotification = newOrder['$v']['sendNotification']['$v'];
|
||||
var processed = newOrder['$v']['processed']['$v'];
|
||||
if (!processed) {
|
||||
context.log('Sending order to orderqueue for order id ', orderId);
|
||||
context.bindings.outputQueue = {
|
||||
orderId: orderId,
|
||||
userId: userId,
|
||||
sendNotification: sendNotification
|
||||
};
|
||||
}
|
||||
else {
|
||||
context.log('Already processed, not sent to orderqueue for order id ', orderId);
|
||||
}
|
||||
}
|
||||
context.done();
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
ProcessOrders function code
|
||||
* Trigger: Azure Storage Queue - orderqueue
|
||||
* Input: Azure Cosmos DB - orders collection
|
||||
* Input: Azure Cosmos DB - users collection
|
||||
* Output: Azure Storage Queue - notificationqueue
|
||||
|
||||
Fires when items are added to the orderqueue. Pulls in the associated order and customer
|
||||
to see if a notification should be sent. If so, adds the order and customer ids to the
|
||||
notificationqueue.
|
||||
*/
|
||||
module.exports = function (context, orderToProcess) {
|
||||
// Do order processing stuff...
|
||||
|
||||
// Send notification that order processing is complete to customer.
|
||||
if (orderToProcess.sendNotification) {
|
||||
var users = context.bindings.users;
|
||||
|
||||
var notificationPhone = '';
|
||||
var firstName = '';
|
||||
for(var i=0; i < users.length; i++) {
|
||||
var user = users[i];
|
||||
if (user['$v']['email']['$v'] === orderToProcess.userId) {
|
||||
if (user['$v']['phone']) {
|
||||
notificationPhone = user['$v']['phone']['$v'];
|
||||
firstName = user['$v']['firstName']['$v'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.bindings.outputQueue = {
|
||||
orderId: orderToProcess.orderId,
|
||||
userId: orderToProcess.userId,
|
||||
notificationPhone: notificationPhone,
|
||||
firstName: firstName
|
||||
};
|
||||
context.log('Sent to notificationqueue for order id ', orderToProcess.orderId);
|
||||
}
|
||||
else {
|
||||
context.log('Notification not requested, not sent to notificationqueue for order id ', orderToProcess.orderId);
|
||||
}
|
||||
|
||||
context.done();
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"virtualMachineName": {
|
||||
"defaultValue": "LabVM",
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "Name to assign to the virtual machine."
|
||||
}
|
||||
},
|
||||
"adminUsername": {
|
||||
"defaultValue": "demouser",
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "User name used to log into the virtual machine"
|
||||
}
|
||||
},
|
||||
"adminPassword": {
|
||||
"defaultValue": "Password.1!!",
|
||||
"type": "securestring",
|
||||
"metadata": {
|
||||
"description": "The password must be at least 10 characters in length and must contain at least one digit, one non-alphanumeric character, and one upper or lower case letter."
|
||||
}
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"cleanVmName": "[replace(replace(replace(toLower(parameters('virtualMachineName')), '-', ''), '_', ''), '.', '')]",
|
||||
"diskSizeGB": "30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"virtualNetworkName": "[concat(resourceGroup().name, '-vnet')]",
|
||||
"virtualMachineSize": "Standard_D2s_v3",
|
||||
"networkInterfaceName": "[concat(variables('cleanVmName'), uniqueString(resourceGroup().id))]",
|
||||
"networkSecurityGroupName": "[concat(variables('cleanVmName'), '-nsg')]",
|
||||
"vnetId": "[resourceId(resourceGroup().name,'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]",
|
||||
"subnetName": "default",
|
||||
"subnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]",
|
||||
"publicIpAddressName": "[concat(variables('cleanVmName'), '-ip')]",
|
||||
"addressPrefix": "172.16.7.0/24",
|
||||
"subnetPrefix": "172.16.7.0/24",
|
||||
"customScriptFileName": "labvmconfig.sh",
|
||||
"customScriptUri": "https://raw.githubusercontent.com/ZoinerTejada/mcw-oss-paas-devops/master/LabVM/labvmconfig.sh"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"name": "[parameters('virtualMachineName')]",
|
||||
"type": "Microsoft.Compute/virtualMachines",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[variables('location')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', variables('networkInterfaceName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"osProfile": {
|
||||
"computerName": "[parameters('virtualMachineName')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"adminPassword": "[parameters('adminPassword')]"
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[variables('virtualMachineSize')]"
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"publisher": "Canonical",
|
||||
"offer": "UbuntuServer",
|
||||
"sku": "16.04-LTS",
|
||||
"version": "latest"
|
||||
},
|
||||
"osDisk": {
|
||||
"createOption": "fromImage",
|
||||
"diskSizeGB": "[variables('diskSizeGB')]",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Premium_LRS"
|
||||
}
|
||||
},
|
||||
"dataDisks": []
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"name": "CustomScriptExtension1",
|
||||
"apiVersion": "2016-03-30",
|
||||
"type": "extensions",
|
||||
"location": "[variables('location')]",
|
||||
"properties": {
|
||||
"publisher": "Microsoft.OSTCExtensions",
|
||||
"type": "CustomScriptForLinux",
|
||||
"typeHandlerVersion": "1.4",
|
||||
"autoUpgradeMinorVersion": true,
|
||||
"settings": {
|
||||
"fileUris": [
|
||||
"[variables('customScriptUri')]"
|
||||
],
|
||||
"commandToExecute": "[concat('bash ', variables('customScriptFileName'))]"
|
||||
}
|
||||
},
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Compute/virtualMachines/', parameters('virtualMachineName'))]"
|
||||
],
|
||||
"tags": {
|
||||
"displayName": "ConfigureLinuxVM"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"type": "Microsoft.Network/virtualNetworks",
|
||||
"apiVersion": "2017-08-01",
|
||||
"location": "[variables('location')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[variables('networkInterfaceName')]",
|
||||
"type": "Microsoft.Network/networkInterfaces",
|
||||
"apiVersion": "2016-09-01",
|
||||
"location": "[variables('location')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]",
|
||||
"[concat('Microsoft.Network/publicIpAddresses/', variables('publicIpAddressName'))]",
|
||||
"[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig1",
|
||||
"properties": {
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
},
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIpAddress": {
|
||||
"id": "[resourceId(resourceGroup().name,'Microsoft.Network/publicIpAddresses', variables('publicIpAddressName'))]"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"networkSecurityGroup": {
|
||||
"id": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[variables('publicIpAddressName')]",
|
||||
"type": "Microsoft.Network/publicIpAddresses",
|
||||
"apiVersion": "2017-08-01",
|
||||
"location": "[variables('location')]",
|
||||
"properties": {
|
||||
"publicIpAllocationMethod": "Dynamic"
|
||||
},
|
||||
"sku": {
|
||||
"name": "Basic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[variables('networkSecurityGroupName')]",
|
||||
"type": "Microsoft.Network/networkSecurityGroups",
|
||||
"apiVersion": "2017-06-01",
|
||||
"location": "[variables('location')]",
|
||||
"properties": {
|
||||
"securityRules": [
|
||||
{
|
||||
"name": "RDP",
|
||||
"properties": {
|
||||
"priority": 100,
|
||||
"protocol": "TCP",
|
||||
"access": "Allow",
|
||||
"direction": "Inbound",
|
||||
"sourceAddressPrefix": "*",
|
||||
"sourcePortRange": "*",
|
||||
"destinationAddressPrefix": "*",
|
||||
"destinationPortRange": "3389"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "default-allow-ssh",
|
||||
"properties": {
|
||||
"priority": 1000,
|
||||
"protocol": "TCP",
|
||||
"access": "Allow",
|
||||
"direction": "Inbound",
|
||||
"sourceAddressPrefix": "*",
|
||||
"sourcePortRange": "*",
|
||||
"destinationAddressPrefix": "*",
|
||||
"destinationPortRange": "22"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"adminUsername": {
|
||||
"type": "string",
|
||||
"value": "[parameters('adminUsername')]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
export DEBIAN_FRONTEND=noninteractive
|
||||
#Install LXDE lxde.org and xrdp - (make sure to open 3389 on the NSG of the azure vm)
|
||||
apt-get update
|
||||
apt-get install -y lxde
|
||||
apt-get install -y xrdp
|
||||
/etc/init.d/xrdp start
|
||||
|
||||
#Prepare XWindows System
|
||||
sed -i 's/allowed_users=console/allowed_users=anybody/' /etc/X11/Xwrapper.config
|
||||
sudo touch ~/.xsession
|
||||
echo "startlxde" > ~/.xsession
|
||||
|
||||
#installing visual studio code which can be launched from accessories
|
||||
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
|
||||
mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
|
||||
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
|
||||
sed -i 's/BIG-REQUESTS/_IG-REQUESTS/' /usr/lib/x86_64-linux-gnu/libxcb.so.1
|
||||
apt-get update
|
||||
apt-get install -y code
|
||||
|
||||
#install the Azure CLI using instructions from Azure.com
|
||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | \
|
||||
sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||
apt-key adv --keyserver packages.microsoft.com --recv-keys 417A0893
|
||||
apt-get install -y apt-transport-https
|
||||
apt-get update && sudo apt-get install -y azure-cli
|
||||
|
||||
#install node.js and npm
|
||||
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
|
||||
apt-get install -y nodejs
|
||||
|
||||
#install MongoDB Community Edition
|
||||
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2930ADAE8CAF5059EE73BB4B58712A2291FA4AD5
|
||||
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.6 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.6.list
|
||||
apt-get update && sudo apt-get install -y mongodb-org
|
||||
sudo service mongod start
|
||||
|
||||
#install docker
|
||||
#to run the az cli container open terminal and use 'sudo docker run -it azuresdk/azure-cli-python:latest'
|
||||
apt-get install -y \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
software-properties-common
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
||||
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"
|
||||
apt-get update
|
||||
apt-get install -y docker-ce
|
|
@ -0,0 +1,53 @@
|
|||
# OSS PaaS and DevOps workshop
|
||||
|
||||
This repository contains resources for use with the OSS PaaS and DevOps workshop, including the starter MERN app, code for Azure Functions, scripts for seeding data into the MongoDB database, as well as exporting data, and an ARM template for deploying the Lab virtual machine (VM) to Azure.
|
||||
|
||||
## LabVM
|
||||
|
||||
To get started, click the Deploy to Azure link below. This will provision a fully configured Linux Lab VM, used as a development machine for the OSS PaaS and DevOps workshop. This should be completed before your workshop.
|
||||
|
||||
[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fzoinertejada%2Fmcw-oss-paas-devops%2Fmaster%2FLabVM%2Fazure-deploy.json)
|
||||
|
||||
VM details:
|
||||
|
||||
1. Ubuntu Server 16.04 LTS
|
||||
2. Docker Community Edition
|
||||
3. Visual Studio Code
|
||||
4. Node.js and NPM
|
||||
5. Mongo DB Community Edition
|
||||
6. Opens port 3389 to allow RDP connections
|
||||
|
||||
## Starter app
|
||||
|
||||
Once the VM is deployed, connect to it using an RDP client, fork the starter application into your own GitHub repo, and clone it to the Lab VM.
|
||||
|
||||
The starter project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
|
||||
|
||||
In the project directory, run:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
node data/Seed.js
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
### `npm install`
|
||||
|
||||
Installs required components.
|
||||
|
||||
### `node data/Seed.js`
|
||||
|
||||
Seeds the local MondoDB database with sample data.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder. It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.
|
||||
|
||||
Your app is ready to be deployed!
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode. You will also see any lint errors in the console.
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
|
@ -0,0 +1,192 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"vmName": {
|
||||
"defaultValue": "LabVM",
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "Name to assign to the virtual machine"
|
||||
}
|
||||
},
|
||||
"vmUsername": {
|
||||
"defaultValue": "demouser",
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "User name used to log into the VM"
|
||||
}
|
||||
},
|
||||
"vmPassword": {
|
||||
"defaultValue": "Password.1!!",
|
||||
"type": "securestring",
|
||||
"metadata": {
|
||||
"description": "The password must be at least 10 characters in length and must contain at least one digit, one non-alphanumeric character, and one upper or lower case letter."
|
||||
}
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"cleanVmName": "[replace(replace(replace(toLower(parameters('vmName')), '-', ''), '_', ''), '.', '')]",
|
||||
"vNetName": "[concat(resourceGroup().name, '-vnet')]",
|
||||
"vnetId": "[resourceId(resourceGroup().name,'Microsoft.Network/virtualNetworks', variables('vNetName'))]",
|
||||
"nicName": "[concat(variables('cleanVmName'), 'nic')]",
|
||||
"nsgName": "LabVM-nsg",
|
||||
"storageAccountName": "[concat(variables('cleanVmName'), subscription().subscriptionId, '-vmstorage')]",
|
||||
"subnetName": "default",
|
||||
"publicIpAddressName": "[concat(variables('cleanVmName'), 'publicip')]",
|
||||
"subnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"name": "[parameters('vmName')]",
|
||||
"type": "Microsoft.Compute/virtualMachines",
|
||||
"location": "[resourceGroup().location]",
|
||||
"apiVersion": "2017-03-30",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]",
|
||||
"[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"osProfile": {
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"adminUsername": "[parameters('vmUsername')]",
|
||||
"adminPassword": "[parameters('vmPassword')]",
|
||||
"windowsConfiguration": {
|
||||
"provisionVMAgent": true,
|
||||
"enableAutomaticUpdates": true
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "Standard_D4s_v3"
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"publisher": "MicrosoftWindowsDesktop",
|
||||
"offer": "Windows-10",
|
||||
"sku": "RS3-Pro",
|
||||
"version": "latest"
|
||||
},
|
||||
"osDisk": {
|
||||
"createOption": "fromImage",
|
||||
"vhd": {
|
||||
"uri": "[concat(concat(reference(resourceId(resourceGroup().name, 'Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-06-15').primaryEndpoints['blob'], 'vhds/'), parameters('vmName'), '20180308161916.vhd')]"
|
||||
},
|
||||
"name": "[parameters('vmName')]"
|
||||
},
|
||||
"dataDisks": []
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"licenseType": "Windows_Client"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[variables('storageAccountName')]",
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"apiVersion": "2017-10-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"sku": {
|
||||
"name": "Premium_LRS",
|
||||
"tier": "Premium"
|
||||
},
|
||||
"dependsOn": [],
|
||||
"tags": {
|
||||
"displayname": "vmattachedstorage"
|
||||
},
|
||||
"kind": "Storage"
|
||||
},
|
||||
{
|
||||
"name": "[variables('vNetName')]",
|
||||
"type": "Microsoft.Network/virtualNetworks",
|
||||
"apiVersion": "2017-10-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"10.0.0.0/16"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "10.0.0.0/24"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.Network/networkInterfaces",
|
||||
"name": "[variables('nicName')]",
|
||||
"apiVersion": "2017-10-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]",
|
||||
"[resourceId('Microsoft.Network/virtualNetworks/', variables('vNetName'))]",
|
||||
"[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig1",
|
||||
"properties": {
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
},
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[variables('publicIpAddressName')]",
|
||||
"type": "Microsoft.Network/publicIPAddresses",
|
||||
"apiVersion": "2017-10-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"properties": {
|
||||
"publicIpAllocationMethod": "Dynamic"
|
||||
},
|
||||
"sku": {
|
||||
"name": "Basic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[variables('nsgName')]",
|
||||
"type": "Microsoft.Network/networkSecurityGroups",
|
||||
"apiVersion": "2017-10-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"properties": {
|
||||
"securityRules": [
|
||||
{
|
||||
"name": "default-allow-rdp",
|
||||
"properties": {
|
||||
"priority": 1000,
|
||||
"protocol": "TCP",
|
||||
"access": "Allow",
|
||||
"direction": "Inbound",
|
||||
"sourceAddressPrefix": "*",
|
||||
"sourcePortRange": "*",
|
||||
"destinationAddressPrefix": "*",
|
||||
"destinationPortRange": "3389"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"vmUsername": {
|
||||
"type": "string",
|
||||
"value": "[parameters('vmUsername')]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
var express = require('express');
|
||||
var path = require('path');
|
||||
var favicon = require('serve-favicon');
|
||||
var logger = require('morgan');
|
||||
var bodyParser = require('body-parser');
|
||||
var session = require('express-session');
|
||||
var mongoStore = require('connect-mongo')(session);
|
||||
|
||||
var order = require('./routes/order');
|
||||
var plan = require('./routes/plan');
|
||||
var user = require('./routes/user');
|
||||
var userSession = require('./routes/session');
|
||||
|
||||
var app = express();
|
||||
|
||||
var databaseUrl = 'mongodb://localhost:27017/best-for-you-organics';
|
||||
|
||||
var mongoose = require('mongoose');
|
||||
mongoose.Promise = require('bluebird');
|
||||
mongoose.connect(databaseUrl, { useMongoClient: true, promiseLibrary: require('bluebird') })
|
||||
.then(() => console.log('connection succesful'))
|
||||
.catch((err) => console.error(err));
|
||||
var db = mongoose.connection;
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ 'extended': 'false' }));
|
||||
app.use(express.static(path.join(__dirname, 'build')));
|
||||
app.use(session(
|
||||
{
|
||||
secret: 'best-for-you-organics',
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
store: new mongoStore({ mongooseConnection: db })
|
||||
}
|
||||
));
|
||||
|
||||
app.use('/api/order', order);
|
||||
app.use('/api/plan', plan);
|
||||
app.use('/api/user', user);
|
||||
app.use('/api/session', userSession);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
var err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function (err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
|
@ -0,0 +1,162 @@
|
|||
var async = require('async');
|
||||
var MongoClient = require('mongodb').MongoClient;
|
||||
var mongoose = require('mongoose');
|
||||
var ObjectId = mongoose.Types.ObjectId;
|
||||
|
||||
var Order = require('../models/Order');
|
||||
var Plan = require('../models/Plan');
|
||||
var User = require('../models/User');
|
||||
|
||||
var databaseUrl = 'mongodb://localhost:27017/best-for-you-organics';
|
||||
|
||||
var twoPersonPlanId = new ObjectId();
|
||||
var fourPersonPlanId = new ObjectId();
|
||||
var highProPlanId = new ObjectId();
|
||||
|
||||
async.series([
|
||||
function (callback) {
|
||||
MongoClient.connect(databaseUrl, function (err, db) {
|
||||
|
||||
if (err) throw err;
|
||||
db.dropDatabase(function (err, result) {
|
||||
db.close(true, function (err, result) {
|
||||
callback(null, 'SUCCESS - dropped database');
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
mongoose.Promise = require('bluebird');
|
||||
mongoose.connect(databaseUrl, { useMongoClient: true, promiseLibrary: require('bluebird') })
|
||||
.then(() => {
|
||||
console.log('Connected to MongoDB database: ' + mongoose.connection.name);
|
||||
callback(null, 'SUCCESS - Connected to MongoDB');
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
console.log('Seeding plans...');
|
||||
|
||||
var plans = [
|
||||
new Plan({
|
||||
_id: twoPersonPlanId,
|
||||
name: 'two_person',
|
||||
friendlyName: 'Two Person Plan',
|
||||
portionSize: '1-2 Person',
|
||||
mealsPerWeek: '3 Unique meals per week',
|
||||
price: 72,
|
||||
description: 'Our basic plan, delivering 3 meals per week, which will feed 1-2 people.'
|
||||
}),
|
||||
new Plan({
|
||||
_id: fourPersonPlanId,
|
||||
name: 'four_person',
|
||||
friendlyName: 'Four Person Plan',
|
||||
portionSize: '3-4 Person',
|
||||
mealsPerWeek: '3 Unique meals per week',
|
||||
price: 87,
|
||||
description: 'Our family plan, delivering 3 meals per week, which will feed 3-4 people.'
|
||||
}),
|
||||
new Plan({
|
||||
_id: highProPlanId,
|
||||
name: 'high_protein',
|
||||
friendlyName: 'High-Pro Plan',
|
||||
portionSize: '1-2 Person',
|
||||
mealsPerWeek: '3 High protein meals per week',
|
||||
price: 80,
|
||||
description: 'Specially formulated for athletes and active individuals, delivering 3 meals per week, for 1-2 people.'
|
||||
})
|
||||
];
|
||||
|
||||
async.eachSeries(plans,
|
||||
function (plan, planSavedCallback) {
|
||||
plan.save(function (err) {
|
||||
if (err) console.dir(err);
|
||||
planSavedCallback();
|
||||
});
|
||||
},
|
||||
function (err) {
|
||||
if (err) console.dir(err);
|
||||
console.log('Finished seeding plans.');
|
||||
callback(null, 'Success - Seed plans');
|
||||
}
|
||||
);
|
||||
},
|
||||
function (callback) {
|
||||
console.log('Seeding users...');
|
||||
|
||||
var users = [
|
||||
new User({
|
||||
email: 'demouser@bfyo.com',
|
||||
password: 'Password.1!!',
|
||||
firstName: 'Demo',
|
||||
lastName: 'User',
|
||||
address1: '123 Main St',
|
||||
city: 'Carmel',
|
||||
state: 'IN',
|
||||
postalCode: '46033'
|
||||
}),
|
||||
new User({
|
||||
email: 'john.smith@bfyo.com',
|
||||
password: 'Password.1!!',
|
||||
firstName: 'John',
|
||||
lastName: 'Smith',
|
||||
address1: '123 Main St',
|
||||
city: 'Virginia Beach',
|
||||
state: 'VA',
|
||||
postalCode: '23456'
|
||||
})
|
||||
];
|
||||
|
||||
async.eachSeries(users,
|
||||
function (user, userSavedCallback) {
|
||||
user.save(function (err) {
|
||||
if (err) console.dir(err);
|
||||
userSavedCallback();
|
||||
});
|
||||
},
|
||||
function (err) {
|
||||
if (err) console.dir(err);
|
||||
console.log('Finished seeding users.');
|
||||
callback(null, 'SUCCESS - Seed Users');
|
||||
}
|
||||
);
|
||||
},
|
||||
function (callback) {
|
||||
console.log('Seeding orders...');
|
||||
|
||||
var orders = [
|
||||
new Order({
|
||||
id : new ObjectId().toHexString(),
|
||||
userId : 'john.smith@bfyo.com',
|
||||
planId : twoPersonPlanId.toHexString(),
|
||||
processed : false,
|
||||
notificationSent : false,
|
||||
sendNotification : false
|
||||
}),
|
||||
new Order({
|
||||
id : new ObjectId().toHexString(),
|
||||
userId : 'demouser@bfyo.com',
|
||||
planId : fourPersonPlanId.toHexString(),
|
||||
processed : false,
|
||||
notificationSent : false,
|
||||
sendNotification : false
|
||||
})
|
||||
];
|
||||
|
||||
async.eachSeries(orders,
|
||||
function(order, orderSavedCallback) {
|
||||
order.save(function (err) {
|
||||
if (err) console.dir(err);
|
||||
orderSavedCallback();
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
if (err) console.dir(err);
|
||||
console.log('Finished seeding orders.');
|
||||
callback(null, 'SUCCESS - Seed Orders');
|
||||
}
|
||||
)
|
||||
}
|
||||
], function (err, results) {
|
||||
console.log('Database seeding complete');
|
||||
process.exit(0);
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
md c:\MongoExport
|
||||
cd c:\Program Files\MongoDB\Server\3.6\bin
|
||||
mongoexport --db best-for-you-organics --collection plans --out c:\MongoExport\plans.json
|
||||
mongoexport --db best-for-you-organics --collection users --out c:\MongoExport\users.json
|
||||
mongoexport --db best-for-you-organics --collection orders --out c:\MongoExport\orders.json
|
|
@ -0,0 +1,3 @@
|
|||
mongoexport --db best-for-you-organics --collection plans --out ~/MongoExport/plans.json
|
||||
mongoexport --db best-for-you-organics --collection users --out ~/MongoExport/users.json
|
||||
mongoexport --db best-for-you-organics --collection orders --out ~/MongoExport/orders.json
|
|
@ -0,0 +1,26 @@
|
|||
var mongoose = require('mongoose');
|
||||
var ObjectId = mongoose.Types.ObjectId;
|
||||
|
||||
// Create the Order schema, showing the shape of the order database entity.
|
||||
var OrderSchema = new mongoose.Schema({
|
||||
id: { type: String, required: false },
|
||||
userId: { type: String, required: true },
|
||||
planId: { type: String, required: true },
|
||||
orderDate: { type: Date, default: Date.now, required: true },
|
||||
sendNotification: { type: Boolean, default: false, required: true },
|
||||
notificationSent: { type: Boolean, default: false, required: true },
|
||||
processed: { type: Boolean, default: false, required: true },
|
||||
dateProcessed: { type: Date, required: false }
|
||||
});
|
||||
|
||||
OrderSchema.pre('save', function (next, err) {
|
||||
var order = this;
|
||||
|
||||
var id = new ObjectId();
|
||||
order.id = id.toHexString();
|
||||
next();
|
||||
});
|
||||
|
||||
// Export the module
|
||||
var Order = mongoose.model('Order', OrderSchema);
|
||||
module.exports = Order;
|
|
@ -0,0 +1,14 @@
|
|||
var mongoose = require('mongoose');
|
||||
|
||||
// Create the Plan schema, showing the shape of the plan database entity.
|
||||
var PlanSchema = new mongoose.Schema({
|
||||
name: { type: String, unique: true, required: true, trim: true },
|
||||
friendlyName: { type: String, required: true, trim: true },
|
||||
portionSize: { type: String, required: true, trim: true },
|
||||
mealsPerWeek: { type: String, required: true, trim: true },
|
||||
price: { type: Number, required: true },
|
||||
description: { type: String, required: false, trim: true }
|
||||
});
|
||||
|
||||
// Export the module
|
||||
module.exports = mongoose.model('Plan', PlanSchema);
|
|
@ -0,0 +1,57 @@
|
|||
var mongoose = require('mongoose');
|
||||
var bcrypt = require('bcryptjs');
|
||||
|
||||
// Create the User schema, showing the shape of the user database entity.
|
||||
var UserSchema = new mongoose.Schema({
|
||||
email: { type: String, unique: true, required: true, trim: true },
|
||||
password: { type: String, required: true },
|
||||
firstName: { type: String, required: true },
|
||||
lastName: { type: String, required: true },
|
||||
address1: { type: String, required: true },
|
||||
address2: { type: String, required: false },
|
||||
city: { type: String, required: true },
|
||||
state: { type: String, required: true },
|
||||
country: { type: String, default: 'US', required: true },
|
||||
postalCode: { type: String, required: true },
|
||||
phone: { type: String, required: false },
|
||||
createdDate: { type: Date, default: Date.now, required: true },
|
||||
modifiedDate: { type: Date, default: Date.now, required: true }
|
||||
});
|
||||
|
||||
UserSchema.pre('save', function (next, err) {
|
||||
var user = this;
|
||||
|
||||
if (!user.isNew && !user.isModified('password')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
var salt = bcrypt.genSaltSync(10);
|
||||
var hash = bcrypt.hashSync(user.password, salt);
|
||||
user.password = hash;
|
||||
next();
|
||||
});
|
||||
|
||||
UserSchema.statics.authenticate = function(email, password, callback) {
|
||||
User.findOne({ 'email': email }, function (err, user) {
|
||||
if(err) return next(err);
|
||||
if (user) {
|
||||
if (bcrypt.compareSync(password, user.password)) {
|
||||
return callback(null, user);
|
||||
}
|
||||
else {
|
||||
var err = new Error('No email and password combination matching what was submitted was found. Please check your email address and password.');
|
||||
err.status = 204;
|
||||
return callback(err);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var err = new Error('No account with that email address was found.');
|
||||
err.status = 204;
|
||||
return callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Export the module
|
||||
var User = mongoose.model('User', UserSchema);
|
||||
module.exports = User;
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "mcw-oss-paas-devops",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"async": "^2.6.2",
|
||||
"axios": "^0.18.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bluebird": "^3.5.4",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^3.3.7",
|
||||
"connect-mongo": "^2.0.3",
|
||||
"express": "^4.16.4",
|
||||
"express-session": "^1.15.6",
|
||||
"mongodb": "^3.2.2",
|
||||
"mongoose": "^5.4.21",
|
||||
"morgan": "^1.9.1",
|
||||
"react": "^16.8.6",
|
||||
"react-bootstrap": "^0.32.4",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^2.1.8",
|
||||
"serve-favicon": "^2.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./start/www",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 5.3 KiB |
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Best for You Organics Company</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"short_name": "Best For You Organics",
|
||||
"name": "Best For You Organcis Company Starter App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var mongoose = require('mongoose');
|
||||
var Order = require('../models/Order.js');
|
||||
|
||||
/* Get single Order by id */
|
||||
router.get('/:id', function (req, res, next) {
|
||||
Order.findById(req.params.id, function (err, order) {
|
||||
if (err) return next(err);
|
||||
res.json(order);
|
||||
});
|
||||
});
|
||||
|
||||
/* Save Order */
|
||||
router.post('/', function (req, res, next) {
|
||||
Order.create(req.body, function (err, order) {
|
||||
if (err) return next(err);
|
||||
res.json({
|
||||
"code": 200,
|
||||
"success": "Order processed successfully.",
|
||||
"order": order
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,22 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var mongoose = require('mongoose');
|
||||
var Plan = require('../models/Plan.js');
|
||||
|
||||
/* Get all Plans */
|
||||
router.get('/', function(req, res, next) {
|
||||
Plan.find(function (err, plans) {
|
||||
if (err) return next(err);
|
||||
res.json(plans);
|
||||
});
|
||||
});
|
||||
|
||||
/* Get single Plan by Id */
|
||||
router.get('/:id', function(req, res, next) {
|
||||
Plan.findById(req.params.id, function (err, plan) {
|
||||
if (err) return next(err);
|
||||
res.json(plan);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,16 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* Get single Order by id */
|
||||
router.get('/', function(req, res, next) {
|
||||
if(req.session && req.session.user) {
|
||||
res.json(req.session.user);
|
||||
}
|
||||
else {
|
||||
res.json({
|
||||
"message":"No user logged in. Need to redirect to login page."
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,96 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var mongoose = require('mongoose');
|
||||
var User = require('../models/User.js');
|
||||
var bcrypt = require('bcryptjs');
|
||||
|
||||
/* Get all Users */
|
||||
router.get('/', function (req, res, next) {
|
||||
User.find(function (err, users) {
|
||||
if (err) return next(err);
|
||||
res.json(users);
|
||||
});
|
||||
});
|
||||
|
||||
/* Get single User by id */
|
||||
router.get('/:id', function (req, res, next) {
|
||||
User.findOne({ 'email': req.params.id }, function (err, user) {
|
||||
if (err) return next(err);
|
||||
res.json(user);
|
||||
});
|
||||
});
|
||||
|
||||
/* Save User */
|
||||
router.post('/', function (req, res, next) {
|
||||
User.create(req.body, function (err, user) {
|
||||
if (err) return next(err);
|
||||
req.session.user = {
|
||||
"id":user.email,
|
||||
"name":user.firstName
|
||||
};
|
||||
|
||||
res.json({
|
||||
"code": 200,
|
||||
"success": "User registered in successfully.",
|
||||
"user": user
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* Update User */
|
||||
router.put('/:id', function (req, res, next) {
|
||||
User.findByIdAndUpdate(req.params.id, req.body, function (err, user) {
|
||||
if (err) return next(err);
|
||||
res.json(user);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/login', function (req, res, next) {
|
||||
User.authenticate(req.body.email, req.body.password, function(err, user) {
|
||||
if(err) return next(err);
|
||||
if (user) {
|
||||
if (bcrypt.compareSync(req.body.password, user.password)) {
|
||||
req.session.user = {
|
||||
"id":user.email,
|
||||
"name":user.firstName
|
||||
};
|
||||
|
||||
res.json({
|
||||
"code": 200,
|
||||
"success": "User logged in successfully.",
|
||||
"user":user
|
||||
});
|
||||
}
|
||||
else {
|
||||
res.json({
|
||||
"code": 204,
|
||||
"failure": "No email and password combination matching what was submitted was found. Please check your email address and password."
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.json({
|
||||
"code": 204,
|
||||
"failure": "No account with that email address was found."
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/logout', function (req, res, next){
|
||||
if(req.session) {
|
||||
req.session.destroy(function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
else {
|
||||
return res.json({
|
||||
"code":200,
|
||||
"success":"User successfully logged out."
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,16 @@
|
|||
import React, { Component } from 'react';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'bootstrap/dist/css/bootstrap-theme.min.css';
|
||||
import Header from './components/header/Header';
|
||||
import Main from './components/Main';
|
||||
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<Main />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
'Hello, world!'
|
||||
, div);
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
main {
|
||||
padding-top: 65px;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import MissingRoute from './MissingRoute';
|
||||
import Order from './order/Order';
|
||||
import Plans from './plan/Plans';
|
||||
import User from './user/User';
|
||||
import './Main.css';
|
||||
|
||||
export default class Main extends Component {
|
||||
render() {
|
||||
return (
|
||||
<main role="main">
|
||||
<Switch>
|
||||
<Route exact path='/' component={Plans}/>
|
||||
<Route path='/order/' component={Order}/>
|
||||
<Route path='/user/' component={User}/>
|
||||
<Route component={MissingRoute}/>
|
||||
</Switch>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
import { Grid } from 'react-bootstrap';
|
||||
|
||||
const MissingRoute = () => (
|
||||
<div>
|
||||
<Grid>
|
||||
<h1>Oops! We could not find the page you're looking for.</h1>
|
||||
<h2>
|
||||
Please use top menu to navigate elsewhere.
|
||||
</h2>
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MissingRoute;
|
|
@ -0,0 +1,7 @@
|
|||
.navbar-brand {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.nav .btn {
|
||||
margin: 10px;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Nav, Navbar, NavItem } from 'react-bootstrap';
|
||||
import axios from 'axios';
|
||||
import logo from '../../logo.png'
|
||||
import './Header.css'
|
||||
|
||||
export default class Header extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
userId: '',
|
||||
userName: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
axios.get('/api/session')
|
||||
.then(res => {
|
||||
console.log(res.data);
|
||||
if (res.data.id) {
|
||||
this.setState({ userId: res.data.id, userName: res.data.name });
|
||||
console.log(this.state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logout(){
|
||||
axios.post('/api/user/logout')
|
||||
.then(res => {
|
||||
console.log('User successfully logged out.');
|
||||
window.location = '/';
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Navbar inverse fixedTop collapseOnSelect>
|
||||
<Navbar.Header>
|
||||
<Navbar.Brand>
|
||||
<Link to="/">
|
||||
<img src={logo} height="40" alt="Best For You Organics Company logo" />
|
||||
</Link>
|
||||
</Navbar.Brand>
|
||||
<Navbar.Toggle />
|
||||
</Navbar.Header>
|
||||
<Navbar.Collapse>
|
||||
<Nav>
|
||||
<NavItem eventKey={1} href="/">
|
||||
Home
|
||||
</NavItem>
|
||||
</Nav>
|
||||
<Nav pullRight>
|
||||
<NavItem eventKey={1} href={this.state.userId ? '/user/' + this.state.userId : 'user/login'}>
|
||||
{this.state.userName ? 'Welcome ' + this.state.userName : 'Sign In'}
|
||||
</NavItem>
|
||||
{this.state.userId ? <button onClick={this.logout.bind(this)} class="btn btn-outline-danger">Logout</button> : ''}
|
||||
{/* <NavItem eventKey={2} href="#">
|
||||
Add Search Box...
|
||||
</NavItem> */}
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import Submit from './Submit';
|
||||
import Thanks from './Thanks';
|
||||
|
||||
export default class Order extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path='/order/thanks/:id' component={Thanks}/>
|
||||
<Route path='/order/:planId' component={Submit}/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
|
||||
export default class Submit extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
id: '',
|
||||
userId: '',
|
||||
planId: this.props.match.params.planId,
|
||||
sendNotification: false,
|
||||
creditCardNumber: '4111111111111111',
|
||||
cvv: '123',
|
||||
userSession: [],
|
||||
plan: {},
|
||||
user: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
axios.get('/api/session')
|
||||
.then(res => {
|
||||
if (!res.data.id) {
|
||||
console.log("You are required to be logged in to place an order.");
|
||||
this.props.history.push("/user/login/");
|
||||
}
|
||||
else {
|
||||
this.setState({ userSession: res.data });
|
||||
console.log(this.state.userSession);
|
||||
this.setState({ userId: this.state.userSession.id });
|
||||
console.log(this.state.userId);
|
||||
|
||||
axios.get('/api/user/' + this.state.userId)
|
||||
.then(result => {
|
||||
this.setState({ user: result.data });
|
||||
console.log(this.state.user);
|
||||
if (this.state.user.phone && this.state.user.phone != '') {
|
||||
const state = this.state;
|
||||
state['sendNotification'] = true;
|
||||
this.setState(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
axios.get('/api/plan/' + this.props.match.params.planId)
|
||||
.then(res => {
|
||||
this.setState({ plan: res.data });
|
||||
console.log(this.state.plan);
|
||||
});
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
const state = this.state
|
||||
state[e.target.name] = e.target.value;
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { orderId, userId, planId, sendNotification, creditCardNumber, cvv } = this.state;
|
||||
|
||||
axios.post('/api/order', { orderId, userId, planId, sendNotification })
|
||||
.then((result) => {
|
||||
if (result.data.code === 200) {
|
||||
this.props.history.push('/order/thanks/'+result.data.order._id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { orderId, userId, planId, sendNotification, creditCardNumber, cvv } = this.state;
|
||||
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Place Order for the {this.state.plan.friendlyName}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4><Link to="/"><span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>Back to all Plans</Link></h4>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div class="form-group">
|
||||
<label for="friendlyName">Selected Plan:</label>
|
||||
<input readOnly type="text" class="form-control" name="friendlyName" value={this.state.plan.friendlyName} />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="creditCardNumber">Credit Card Number:</label>
|
||||
<input type="number" class="form-control" name="creditCardNumber" value={creditCardNumber} onChange={this.onChange} placeholder="Credit Card Number" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cvv">CVV:</label>
|
||||
<input type="password" class="form-control" name="cvv" value={cvv} onChange={this.onChange} placeholder="CVV" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Place Order</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Panel } from 'react-bootstrap';
|
||||
import axios from 'axios';
|
||||
|
||||
export default class Thanks extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
order: {},
|
||||
plan: {},
|
||||
user: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
axios.get('/api/order/' + this.props.match.params.id)
|
||||
.then(res => {
|
||||
console.log('Order data: ', res.data);
|
||||
this.setState({ order: res.data });
|
||||
console.log('State order: ', this.state.order);
|
||||
|
||||
axios.get('/api/user/' + this.state.order.userId)
|
||||
.then(resUser => {
|
||||
this.setState({ user: resUser.data });
|
||||
});
|
||||
|
||||
axios.get('/api/plan/' + this.state.order.planId)
|
||||
.then(resPlan => {
|
||||
this.setState({ plan: resPlan.data });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{'Thanks for your order, ' + this.state.user.firstName + '!'}
|
||||
</h3>
|
||||
Your order will ship soon.
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl>
|
||||
<dt>Order Id:</dt>
|
||||
<dd>{this.state.order.id}</dd>
|
||||
<dt>Plan:</dt>
|
||||
<dd>{this.state.plan.friendlyName}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,392 @@
|
|||
.well {
|
||||
max-width: 392px;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.plans {
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px
|
||||
}
|
||||
|
||||
.plan-wrapper {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 375px;
|
||||
margin: 0 auto;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px
|
||||
}
|
||||
|
||||
.plan {
|
||||
display: block;
|
||||
cursor: inherit;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
color: #444;
|
||||
border: 1px solid #ddd;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 98%;
|
||||
background-image: none;
|
||||
-webkit-box-shadow: 0 4px 6px 0 rgba(0, 0, 0, .1);
|
||||
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.plan .logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.plan .persons {
|
||||
margin: 0 0 5px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
line-height: 1.1em
|
||||
}
|
||||
|
||||
.plan .meals-per-week {
|
||||
margin: 0 10px 5px;
|
||||
font-size: 13px;
|
||||
font-weight: 500
|
||||
}
|
||||
|
||||
.plan .description {
|
||||
padding: 5px 0 0;
|
||||
margin: 8px;
|
||||
height: 260px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.plan .name {
|
||||
margin: 0 0 5px;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 1.1em;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.plan .plan-price {
|
||||
padding: 0;
|
||||
display: block;
|
||||
margin: 5px 12px 12px;
|
||||
}
|
||||
|
||||
.plan .price {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.plan .week {
|
||||
font-size: 13px;
|
||||
line-height: 2.46
|
||||
}
|
||||
|
||||
.plan .select-btn {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.how-it-works .content {
|
||||
padding: 10px 15px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
@media only screen and (min-width:992px) {
|
||||
.how-it-works .content {
|
||||
padding: 40px 0 20px
|
||||
}
|
||||
}
|
||||
|
||||
.how-it-works .title {
|
||||
color: #e7a662;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 30px;
|
||||
line-height: 34px;
|
||||
text-transform: uppercase;
|
||||
padding: 0;
|
||||
margin: 30px 0
|
||||
}
|
||||
|
||||
@media only screen and (min-width:768px) {
|
||||
.how-it-works .title {
|
||||
font-size: 30px;
|
||||
line-height: 34px
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width:992px) {
|
||||
.how-it-works .title {
|
||||
font-size: 36px;
|
||||
line-height: 40px
|
||||
}
|
||||
}
|
||||
|
||||
.how-it-works .list {
|
||||
max-width: 440px;
|
||||
margin: 30px auto;
|
||||
padding: 0;
|
||||
list-style: none
|
||||
}
|
||||
|
||||
@media only screen and (min-width:992px) {
|
||||
.how-it-works .list {
|
||||
max-width: inherit;
|
||||
margin: 50px auto;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: start;
|
||||
-ms-flex-align: start;
|
||||
align-items: flex-start;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center
|
||||
}
|
||||
}
|
||||
|
||||
.how-it-works .list li {
|
||||
position: relative;
|
||||
padding-bottom: 40px;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
text-align: left
|
||||
}
|
||||
|
||||
.how-it-works .list li:last-child {
|
||||
padding-bottom: 0
|
||||
}
|
||||
|
||||
@media only screen and (min-width:992px) {
|
||||
.how-it-works .list li {
|
||||
display: block;
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
margin: 0 70px
|
||||
}
|
||||
}
|
||||
|
||||
.how-it-works .list .icon {
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
margin: 0 25px;
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 auto;
|
||||
flex: 0 0 auto
|
||||
}
|
||||
|
||||
@media only screen and (min-width:992px) {
|
||||
.how-it-works .list .icon {
|
||||
width: 76px;
|
||||
height: 76px;
|
||||
margin: 0 0 30px
|
||||
}
|
||||
}
|
||||
|
||||
.how-it-works .list .title {
|
||||
color: #e7a662;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
text-transform: uppercase;
|
||||
padding: 0;
|
||||
margin: 0 0 10px
|
||||
}
|
||||
|
||||
@media only screen and (min-width:768px) {
|
||||
.how-it-works .list .title {
|
||||
font-size: 18px;
|
||||
line-height: 22px
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width:992px) {
|
||||
.how-it-works .list .title {
|
||||
font-size: 18px;
|
||||
line-height: 22px
|
||||
}
|
||||
}
|
||||
|
||||
.how-it-works .list .text {
|
||||
color: #444;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.how-it-works .list .nowrap {
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
.plan.selected {
|
||||
z-index: 1;
|
||||
border: 2px solid #444;
|
||||
border-left: 0;
|
||||
border-right: 0
|
||||
}
|
||||
|
||||
@media only screen and (min-width:768px) {
|
||||
.plan.selected {
|
||||
border: 2px solid #444
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width:992px) {
|
||||
.plan.selected {
|
||||
border: 3px solid #444
|
||||
}
|
||||
}
|
||||
|
||||
.plan.selected:before {
|
||||
content: "currently selected";
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
background: #444;
|
||||
padding: 4px 5px 4px 6px;
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 9px
|
||||
}
|
||||
|
||||
@media only screen and (min-width:768px) {
|
||||
.plan.selected:before {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
padding: 6px 13px 4px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
top: inherit;
|
||||
bottom: 100%;
|
||||
left: -2px;
|
||||
right: -2px
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width:992px) {
|
||||
.plan.selected:before {
|
||||
left: -3px;
|
||||
right: -3px
|
||||
}
|
||||
}
|
||||
|
||||
.plan .plan-img {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
margin: 9px;
|
||||
width: 33%
|
||||
}
|
||||
|
||||
@media only screen and (min-width:768px) {
|
||||
.plan .plan-img {
|
||||
float: none;
|
||||
margin: 0;
|
||||
width: auto
|
||||
}
|
||||
}
|
||||
|
||||
.plan .plan-img img {
|
||||
max-width: 100%
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px
|
||||
}
|
||||
|
||||
.container:after, .container:before {
|
||||
content: " ";
|
||||
display: table
|
||||
}
|
||||
|
||||
.container:after {
|
||||
clear: both
|
||||
}
|
||||
|
||||
@media (min-width:768px) {
|
||||
.container {
|
||||
width: 750px
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:992px) {
|
||||
.container {
|
||||
width: 970px
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:1200px) {
|
||||
.container {
|
||||
width: 1170px
|
||||
}
|
||||
}
|
||||
|
||||
.jumbotron {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: 30px;
|
||||
background-color: #eee
|
||||
}
|
||||
|
||||
.jumbotron, .jumbotron .h1, .jumbotron h1 {
|
||||
color: inherit
|
||||
}
|
||||
|
||||
.jumbotron p {
|
||||
margin-bottom: 15px;
|
||||
font-size: 21px;
|
||||
font-weight: 200
|
||||
}
|
||||
|
||||
.jumbotron>hr {
|
||||
border-top-color: #d5d5d5
|
||||
}
|
||||
|
||||
.container-fluid .jumbotron, .container .jumbotron {
|
||||
border-radius: 6px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px
|
||||
}
|
||||
|
||||
.jumbotron .container {
|
||||
max-width: 100%
|
||||
}
|
||||
|
||||
@media screen and (min-width:768px) {
|
||||
.jumbotron {
|
||||
padding-top: 48px;
|
||||
padding-bottom: 48px
|
||||
}
|
||||
.container-fluid .jumbotron, .container .jumbotron {
|
||||
padding-left: 60px;
|
||||
padding-right: 60px
|
||||
}
|
||||
.jumbotron .h1, .jumbotron h1 {
|
||||
font-size: 63px
|
||||
}
|
||||
} */
|
|
@ -0,0 +1,96 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import { Col, Grid, Row, Well } from 'react-bootstrap';
|
||||
import logo from '../../logo.png';
|
||||
import './Plans.css';
|
||||
|
||||
export default class Plans extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
plans: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
axios.get('/api/plan')
|
||||
.then(res => {
|
||||
this.setState({ plans: res.data });
|
||||
console.log(this.state.plans);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const plans = this.state.plans;
|
||||
|
||||
let planLogo = <img src={logo} alt="Best For You Organics Company logo" class="logo" />;
|
||||
|
||||
const planCatalog = plans.map(plan => {
|
||||
return (
|
||||
<Col xs={12} sm={4} md={4}>
|
||||
<Well>
|
||||
<div class="plan-wrapper">
|
||||
<div class="plan">
|
||||
<div class="name">{plan.friendlyName}</div>
|
||||
<div class="description">
|
||||
<div class="plan-details">
|
||||
{planLogo}
|
||||
<h2 class="persons">{plan.portionSize}</h2>
|
||||
<div class="meals-per-week">{plan.mealsPerWeek}</div>
|
||||
</div>
|
||||
<div>{plan.description}</div>
|
||||
</div>
|
||||
<div class="plan-price">
|
||||
<div class="price-block">
|
||||
<span class="price">${plan.price}</span>
|
||||
<span class="week">/Week</span>
|
||||
</div>
|
||||
<Link to={'/order/' + plan._id} class="btn btn-warning">Select this plan</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Well>
|
||||
</Col>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="container">
|
||||
<Grid>
|
||||
<div class="plans">
|
||||
<Row>{planCatalog}</Row>
|
||||
</div>
|
||||
</Grid>
|
||||
<hr />
|
||||
|
||||
<div class="how-it-works">
|
||||
<div class="content">
|
||||
<h2 class="title">How It Works</h2>
|
||||
<ul class="list">
|
||||
<li>
|
||||
<div class="text-container">
|
||||
<div class="title">We Create</div>
|
||||
<div class="text">Our chefs create delicious, whole-foods-based recipes with 100% natural ingredients.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="text-container">
|
||||
<div class="title">We Deliver</div>
|
||||
<div class="text">Each week, we will send you a variety of meals, made up of fresh ingredients in the correct portions.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="text-container">
|
||||
<div class="title">You Cook</div>
|
||||
<div class="text">You will perform a little be of prep work, and cook unique, amazing meals that you’ll love.</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
import React, { Component } from 'react';
|
||||
import axios from 'axios';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default class Edit extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
axios.get('/api/user/' + this.props.match.params.id)
|
||||
.then(res => {
|
||||
this.setState({ user: res.data });
|
||||
console.log(this.state.user);
|
||||
});
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
const state = this.state.user
|
||||
state[e.target.name] = e.target.value;
|
||||
this.setState({ user: state });
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { firstName, lastName, email, password, address1, address2, city, state, country, postalCode, phone } = this.state.user;
|
||||
|
||||
axios.put('/api/user/' + this.props.match.params.id, { firstName, lastName, email, password, address1, address2, city, state, country, postalCode, phone })
|
||||
.then((result) => {
|
||||
this.props.history.push("/show/" + this.props.match.params.id)
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Edit Your Information
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div class="form-group">
|
||||
<label for="firstName">First Name:</label>
|
||||
<input type="text" class="form-control" name="firstName" value={this.state.user.firstName} onChange={this.onChange} placeholder="First name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastName">Last Name:</label>
|
||||
<input type="text" class="form-control" name="lastName" value={this.state.user.lastName} onChange={this.onChange} placeholder="Last name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address:</label>
|
||||
<input type="email" class="form-control" name="email" value={this.state.user.email} onChange={this.onChange} placeholder="Email address" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" class="form-control" name="password" value={this.state.user.password} onChange={this.onChange} placeholder="Password" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="address1">Address:</label>
|
||||
<input type="text" class="form-control" name="address1" value={this.state.user.address1} onChange={this.onChange} placeholder="Address line 1" />
|
||||
<input type="text" class="form-control" name="address2" value={this.state.user.address2} onChange={this.onChange} placeholder="Address line 2" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="city">City:</label>
|
||||
<input type="text" class="form-control" name="city" value={this.state.user.city} onChange={this.onChange} placeholder="City" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="state">State/Province:</label>
|
||||
<input type="text" class="form-control" name="state" value={this.state.user.state} onChange={this.onChange} placeholder="State/Province" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="country">Country:</label>
|
||||
<input type="text" class="form-control" name="country" value={this.state.user.country} onChange={this.onChange} placeholder="Country" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="postalCode">Postal Code:</label>
|
||||
<input type="text" class="form-control" name="postalCode" value={this.state.user.postalCode} onChange={this.onChange} placeholder="Postal code" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone:</label>
|
||||
<input type="text" class="form-control" name="phone" value={this.state.user.phone} onChange={this.onChange} placeholder="Phone number" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Save Changes</button>
|
||||
<Link to={`/user/${this.state.user._id}`} class="btn btn-warning">Cancel</Link>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import axios from 'axios';
|
||||
|
||||
export default class Login extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
email: '',
|
||||
password: ''
|
||||
}
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
const state = this.state
|
||||
state[e.target.name] = e.target.value;
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { email, password } = this.state;
|
||||
|
||||
axios.post('/api/user/login', { email, password })
|
||||
.then((result) => {
|
||||
if (result.data.code === 200) {
|
||||
console.log(result.data.success);
|
||||
window.location = '/';
|
||||
}
|
||||
else if (result.data.code === 204) {
|
||||
alert(result.data.failure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
register = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.history.push("/user/register/");
|
||||
}
|
||||
|
||||
render() {
|
||||
const { email, password } = this.state;
|
||||
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Login
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address:</label>
|
||||
<input type="email" class="form-control" name="email" value={email} onChange={this.onChange} placeholder="Email address" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" class="form-control" name="password" value={password} onChange={this.onChange} />
|
||||
</div>
|
||||
<Button type="submit" bsClass="default">Login</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<div>Not registered yet? Register now!</div>
|
||||
<Button bsClass="default" onClick={(event) => this.register(event)}>Register</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import React, { Component } from 'react';
|
||||
import axios from 'axios';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default class Profile extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
axios.get('/api/user/'+this.props.match.params.id)
|
||||
.then(res => {
|
||||
this.setState({ user: res.data });
|
||||
console.log(this.state.user);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{this.state.user.firstName + ' ' + this.state.user.lastName}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl>
|
||||
<dt>User Id:</dt>
|
||||
<dd>{this.state.user._id}</dd>
|
||||
<dt>First Name:</dt>
|
||||
<dd>{this.state.user.firstName}</dd>
|
||||
<dt>Last Name:</dt>
|
||||
<dd>{this.state.user.lastName}</dd>
|
||||
<dt>Email Address:</dt>
|
||||
<dd>{this.state.user.email}</dd>
|
||||
<dt>Address1:</dt>
|
||||
<dd>{this.state.user.address1}</dd>
|
||||
<dt>Address2:</dt>
|
||||
<dd>{this.state.user.address2}</dd>
|
||||
<dt>City:</dt>
|
||||
<dd>{this.state.user.city}</dd>
|
||||
<dt>State/Province:</dt>
|
||||
<dd>{this.state.user.state}</dd>
|
||||
<dt>Country:</dt>
|
||||
<dd>{this.state.user.country}</dd>
|
||||
<dt>Postal Code:</dt>
|
||||
<dd>{this.state.user.postalCode}</dd>
|
||||
<dt>Phone:</dt>
|
||||
<dd>{this.state.user.phone}</dd>
|
||||
</dl>
|
||||
<Link to={`/user/edit/${this.state.user._id}`} class="btn btn-warning">Edit</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import React, { Component } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
export default class Register extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
password: 'Password.1!!',
|
||||
address1: '123 Main St.',
|
||||
address2: '',
|
||||
city: 'Seattle',
|
||||
state: 'WA',
|
||||
country: 'US',
|
||||
postalCode: '98101',
|
||||
phone: ''
|
||||
};
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
const state = this.state
|
||||
state[e.target.name] = e.target.value;
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { firstName, lastName, email, password, address1, address2, city, state, country, postalCode, phone } = this.state;
|
||||
|
||||
axios.post('/api/user', { firstName, lastName, email, password, address1, address2, city, state, country, postalCode, phone })
|
||||
.then((result) => {
|
||||
if (result.data.code === 200) {
|
||||
console.log("User registration successful.");
|
||||
window.location = '/';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { firstName, lastName, email, password, address1, address2, city, state, country, postalCode, phone } = this.state;
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Register
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div class="form-group">
|
||||
<label for="firstName">First Name:</label>
|
||||
<input type="text" class="form-control" name="firstName" value={firstName} onChange={this.onChange} placeholder="First name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastName">Last Name:</label>
|
||||
<input type="text" class="form-control" name="lastName" value={lastName} onChange={this.onChange} placeholder="Last name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address:</label>
|
||||
<input type="email" class="form-control" name="email" value={email} onChange={this.onChange} placeholder="Email address" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" class="form-control" name="password" value={password} onChange={this.onChange} placeholder="Password" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="address1">Address:</label>
|
||||
<input type="text" class="form-control" name="address1" value={address1} onChange={this.onChange} placeholder="Address line 1" />
|
||||
<input type="text" class="form-control" name="address2" value={address2} onChange={this.onChange} placeholder="Address line 2" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="city">City:</label>
|
||||
<input type="text" class="form-control" name="city" value={city} onChange={this.onChange} placeholder="City" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="state">State/Province:</label>
|
||||
<input type="text" class="form-control" name="state" value={state} onChange={this.onChange} placeholder="State/Province" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="country">Country:</label>
|
||||
<input type="text" class="form-control" name="country" value={country} onChange={this.onChange} placeholder="Country" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="postalCode">Postal Code:</label>
|
||||
<input type="text" class="form-control" name="postalCode" value={postalCode} onChange={this.onChange} placeholder="Postal code" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone:</label>
|
||||
<input type="text" class="form-control" name="phone" value={phone} onChange={this.onChange} placeholder="Phone number" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import Profile from './Profile';
|
||||
import Login from './Login';
|
||||
import Register from './Register';
|
||||
import Edit from './Edit';
|
||||
|
||||
export default class User extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path='/user/edit/:id' component={Edit}/>
|
||||
<Route path='/user/login' component={Login}/>
|
||||
<Route path='/user/register' component={Register}/>
|
||||
<Route path='/user/:id' component={Profile}/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import registerServiceWorker from './registerServiceWorker';
|
||||
|
||||
ReactDOM.render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
registerServiceWorker();
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 125 KiB |
|
@ -0,0 +1,114 @@
|
|||
// In production, we register a service worker to serve assets from local cache.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||
// cached resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||
// This link also includes instructions on opting out of this behavior.
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export default function register() {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl);
|
||||
} else {
|
||||
// Is not local host. Just register service worker
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('fetch', (event) => {
|
||||
if ( event.request.url.match( '^.*(\/api\/).*$' ) ) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the old content will have been purged and
|
||||
// the fresh content will have been added to the cache.
|
||||
// It's the perfect time to display a "New content is
|
||||
// available; please refresh." message in your web app.
|
||||
console.log('New content is available; please refresh.');
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
if (
|
||||
response.status === 404 ||
|
||||
response.headers.get('content-type').indexOf('javascript') === -1
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('mean-app:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
Загрузка…
Ссылка в новой задаче