├── .babelrc
├── .editorconfig
├── .envrc.example
├── .gitignore
├── LICENSE
├── README.md
├── circle.yml
├── env.config.js
├── jsconfig.json
├── package-lock.json
├── package.json
├── src
├── components
│ ├── shared
│ │ ├── auth
│ │ │ ├── components
│ │ │ │ ├── footer.js
│ │ │ │ ├── quotes.js
│ │ │ │ ├── social-buttons.js
│ │ │ │ └── style.js
│ │ │ ├── forgot-password.js
│ │ │ ├── set-password.js
│ │ │ ├── signin.js
│ │ │ └── signup.js
│ │ ├── cli.js
│ │ ├── cta.js
│ │ ├── footer.js
│ │ ├── header
│ │ │ ├── hexagons.svg
│ │ │ ├── index.js
│ │ │ ├── site-nav.js
│ │ │ └── user-nav.js
│ │ ├── testimonials
│ │ │ ├── icon.svg
│ │ │ └── index.js
│ │ └── youTube.js
│ └── ui
│ │ ├── avatar.js
│ │ ├── button.js
│ │ ├── github-button.js
│ │ ├── grid.js
│ │ ├── head.js
│ │ ├── hexagon.js
│ │ ├── input-list.js
│ │ ├── input.js
│ │ ├── link.js
│ │ ├── loader.js
│ │ ├── logo
│ │ ├── index.js
│ │ └── logo.svg
│ │ ├── markdown.js
│ │ ├── menu.js
│ │ ├── modal.js
│ │ ├── nav-link.js
│ │ ├── nav.js
│ │ ├── page
│ │ ├── index.js
│ │ └── styles
│ │ │ ├── font-awesome.js
│ │ │ └── normalize.js
│ │ ├── router.js
│ │ ├── scroll-manager.js
│ │ ├── sidebar.js
│ │ ├── slider.js
│ │ └── text.js
├── index.html
├── index.js
├── services
│ ├── auth.service.js
│ ├── request.service.js
│ └── ui.service.js
├── static
│ ├── fonts
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.svg
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ └── fontawesome-webfont.woff2
│ ├── img
│ │ ├── enterprise
│ │ │ ├── afm_logo.png
│ │ │ ├── altibox_logo.png
│ │ │ ├── aws_logo.png
│ │ │ ├── bank.svg
│ │ │ ├── check-circle.svg
│ │ │ ├── code-1.svg
│ │ │ ├── cursor-double-click.svg
│ │ │ ├── dnb_logo.jpg
│ │ │ ├── graduation-cap.svg
│ │ │ ├── server-check.svg
│ │ │ ├── transformation.svg
│ │ │ └── window-code.svg
│ │ ├── features
│ │ │ ├── clouds.jpg
│ │ │ └── dashboard.png
│ │ ├── og-image.png
│ │ ├── slack-invite
│ │ │ ├── og-image.png
│ │ │ ├── syncano-slack.svg
│ │ │ └── twitter-image.png
│ │ ├── syncano-logo-dark.png
│ │ └── testimonials
│ │ │ ├── artur-czmiel.png
│ │ │ ├── artur-czmiel@2x.png
│ │ │ ├── halvor-lande.png
│ │ │ ├── halvor-lande@2x.png
│ │ │ ├── ingar-bentzen.png
│ │ │ ├── ingar-bentzen@2x.png
│ │ │ ├── nikolai-fasting.png
│ │ │ ├── nikolai-fasting@2x.png
│ │ │ ├── stale-husby.png
│ │ │ ├── stale-husby@2x.png
│ │ │ ├── sven-sunde.png
│ │ │ └── sven-sunde@2x.png
│ └── sitemap.xml
├── stores
│ ├── auth.store.js
│ ├── messages.store.js
│ ├── pending.store.js
│ └── ui.store.js
└── views
│ ├── enterprise
│ ├── components
│ │ ├── book-meeting.js
│ │ ├── enterpriseComponents.js
│ │ ├── enterpriseCta.js
│ │ ├── enterpriseFeatures.js
│ │ ├── enterpriseGraph.js
│ │ └── enterpriseTestimonials.js
│ └── index.js
│ ├── faq.js
│ ├── features
│ ├── components
│ │ ├── automation-sdk-section.js
│ │ ├── cloud-os-section.js
│ │ ├── community-section.js
│ │ ├── content-nav
│ │ │ ├── content-nav-item.js
│ │ │ ├── icon-0.svg
│ │ │ ├── icon-1.svg
│ │ │ ├── icon-2.svg
│ │ │ ├── icon-3.svg
│ │ │ └── index.js
│ │ └── registries-section
│ │ │ ├── hexagons.svg
│ │ │ ├── icon-db.svg
│ │ │ ├── icon-fb.svg
│ │ │ ├── icon-sentry.svg
│ │ │ ├── icon-stripe.svg
│ │ │ ├── icon-twitter.svg
│ │ │ └── index.js
│ ├── featuresPage.service.js
│ ├── featuresPage.store.js
│ ├── helpers.js
│ └── index.js
│ ├── landing
│ ├── index.js
│ └── sections
│ │ ├── faq.js
│ │ ├── features
│ │ ├── icon-automation-sdk.svg
│ │ ├── icon-cloud-os.svg
│ │ ├── icon-community.svg
│ │ ├── icon-socket-registry.svg
│ │ └── index.js
│ │ ├── innovation.js
│ │ └── sockets
│ │ ├── hexagons.svg
│ │ ├── icon-db.svg
│ │ ├── icon-fb.svg
│ │ ├── icon-firebase.svg
│ │ ├── icon-google-analytics.svg
│ │ ├── icon-parse.svg
│ │ ├── icon-segment.svg
│ │ ├── icon-sentry.svg
│ │ ├── icon-slack.svg
│ │ ├── icon-stripe.svg
│ │ ├── icon-twitter.svg
│ │ └── index.js
│ ├── missing.js
│ ├── pricing
│ ├── components
│ │ ├── arrow.svg
│ │ ├── pricing-estimate.js
│ │ └── pricing-table.js
│ ├── index.js
│ └── sections
│ │ ├── faq.js
│ │ └── scaling.js
│ ├── signin.js
│ ├── signup.js
│ ├── slack-invite
│ ├── index.js
│ ├── slackInvite.service.js
│ └── slackInviteForm.js
│ ├── sockets
│ ├── components
│ │ ├── details.js
│ │ ├── hexagon.js
│ │ ├── icon.svg
│ │ ├── list-item.js
│ │ ├── list.js
│ │ ├── search.js
│ │ ├── sidebar.js
│ │ └── sort-list.js
│ ├── index.js
│ ├── sockets.service.js
│ └── sockets.store.js
│ └── terms
│ ├── acceptable-use-policy.js
│ ├── components
│ └── sidebar.js
│ ├── general-terms.js
│ ├── privacy-policy.js
│ ├── syncano-performance-insights.js
│ └── terms-of-service.js
├── syncano
├── .gitignore
└── syncano.yml
├── yarn.lock
└── zefir.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "zefir/babel"
4 | ],
5 | "plugins": [
6 | ["transform-define", "./env.config.js"]
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.envrc.example:
--------------------------------------------------------------------------------
1 | export HELLO_FACEBOOK=
2 | export HELLO_GOOGLE=
3 | export HELLO_GITHUB=
4 | export HELLO_REDIRECT_URI=
5 | export DASHBOARD_URL=
6 | export API_URL=
7 | export ANALYTICS_WRITE_KEY=
8 | export OPTIMIZELY_KEY=
9 | export SYNCANO_REGISTRY_URL=https://socket-registry.syncano.link
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Build
7 | .zefir
8 |
9 | # Dependency directories
10 | node_modules
11 |
12 | # Other
13 | .DS_Store
14 | Thumbs.db
15 | .envrc
16 | .vscode
17 |
18 | # Syncano
19 | syncano/.dist
20 | syncano/.bundles
21 | syncano/**/scripts/.bundles
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Syncano
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # syncano.com
2 | [](https://www.syncano.io/#/slack-invite/)
3 | [](https://circleci.com/gh/Syncano/syncano.com/tree/master)
4 |
5 | [Syncano.com](https://www.syncano.io/) Website
6 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | environment:
3 | YARN_VERSION: 0.17.10
4 | PATH: "${PATH}:${HOME}/.yarn/bin"
5 |
6 | dependencies:
7 | pre:
8 | - case $CIRCLE_NODE_INDEX in 0) NODE_VERSION=7 ;; 1) NODE_VERSION=6 ;; 2) NODE_VERSION=4 ;; esac; nvm install $NODE_VERSION && nvm alias default $NODE_VERSION
9 | - |
10 | if [[ ! -e ~/.yarn/bin/yarn || $(yarn --version) != "${YARN_VERSION}" ]]; then
11 | curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version $YARN_VERSION
12 | fi
13 | cache_directories:
14 | - ~/.yarn
15 | - ~/.yarn-cache
16 | override:
17 | - yarn
18 | - yarn global add syncano-cli@beta
19 | # the line below is a hack for permissions issues in zefir package. remove when fixed.
20 | - chmod -R u+x node_modules
21 | test:
22 | override:
23 | - yarn run build
24 |
25 | deployment:
26 | production:
27 | branch: syncano-ascend-master
28 | commands:
29 | - syncano-cli deploy
30 | - syncano-cli hosting config staging --cname syncano.rocks
31 | - syncano-cli hosting config production --cname syncano.io
32 | - syncano-cli hosting sync production
33 | staging:
34 | branch: syncano-ascend
35 | commands:
36 | - syncano-cli deploy
37 | - syncano-cli hosting config staging --cname syncano.rocks
38 | - syncano-cli hosting config production --cname syncano.io
39 | - syncano-cli hosting sync staging
40 |
41 | experimental:
42 | notify:
43 | branches:
44 | only:
45 | - syncano-ascend
46 |
--------------------------------------------------------------------------------
/env.config.js:
--------------------------------------------------------------------------------
1 | const ENV = {
2 | 'syncano-ascend': 'ASCEND',
3 | 'syncano-ascend-master': 'PRODUCTION',
4 | devel: 'STAGING',
5 | master: 'PRODUCTION'
6 | }[process.env.CIRCLE_BRANCH || 'devel']
7 |
8 | const envVars = [
9 | 'HELLO_FACEBOOK',
10 | 'HELLO_GOOGLE',
11 | 'HELLO_GITHUB',
12 | 'HELLO_REDIRECT_URI',
13 | 'DASHBOARD_URL',
14 | 'ANALYTICS_WRITE_KEY',
15 | 'OPTIMIZELY_KEY',
16 | 'API_URL',
17 | 'SYNCANO_REGISTRY_URL'
18 | ]
19 |
20 | const variables = envVars
21 | .reduce((env, key) => {
22 | const name = ENV ? `${ENV}_${key}` : key
23 | const value = process.env[name] || process.env[key]
24 |
25 | return Object.assign({}, env, {
26 | [`process.env.${key}`]: value
27 | })
28 | }, {})
29 |
30 | module.exports = variables
31 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "experimentalDecorators": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "syncano.com",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "zefir",
7 | "build": "zefir build",
8 | "start": "zefir start"
9 | },
10 | "dependencies": {
11 | "axios": "0.16.1",
12 | "color": "^2.0.0",
13 | "hellojs": "1.14.1",
14 | "js-cookie": "^2.1.4",
15 | "lodash.get": "^4.4.2",
16 | "md5": "^2.2.1",
17 | "mobx": "^3.1.9",
18 | "mobx-react": "^4.1.8",
19 | "rc-slider": "^8.2.0",
20 | "react": "^15.5.4",
21 | "react-dom": "^15.5.4",
22 | "react-github-button": "0.1.11",
23 | "react-hexagon": "1.1.1",
24 | "react-marked-markdown": "^1.4.2",
25 | "react-rotating-text": "^1.2.0",
26 | "react-router": "~4.2.0",
27 | "react-sticky": "^5.0.8",
28 | "svg-react-loader": "0.4.0",
29 | "syncano-cli": "beta",
30 | "zefir": "beta"
31 | },
32 | "devDependencies": {
33 | "babel-eslint": "^7.2.3",
34 | "babel-plugin-inline-react-svg": "0.4.0",
35 | "babel-plugin-transform-define": "^1.2.0",
36 | "html-webpack-plugin": "^2.30.1",
37 | "mobx-react-devtools": "^4.2.11",
38 | "standard": "^10.0.2"
39 | },
40 | "standard": {
41 | "parser": "babel-eslint"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/shared/auth/components/footer.js:
--------------------------------------------------------------------------------
1 | const Footer = ({toggleModal, smallSpacing}) => (
2 |
6 |
7 | By signing up you confirm that you accept our
Terms of Use
8 |
9 |
10 |
16 |
17 | )
18 |
19 | export default Footer
20 |
--------------------------------------------------------------------------------
/src/components/shared/auth/components/social-buttons.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import Button from '../../../ui/button'
3 | import InputList from '../../../ui/input-list'
4 | import Grid from '../../../ui/grid'
5 |
6 | const SocialButtons = ({authViaGithub, authViaGoogle, authViaFacebook, messages}) => (
7 |
8 |
9 |
10 |
11 |
12 | Github
13 |
14 |
15 |
16 |
17 |
18 |
19 | Google
20 |
21 |
22 |
23 |
24 |
25 | Facebook
26 |
27 |
28 |
29 |
30 |
31 |
32 |
73 |
74 | )
75 |
76 | SocialButtons.init = ({
77 | stores: {messages},
78 | services: {auth: {socialAuth}}
79 | }) => ({
80 | messages,
81 | authViaFacebook: socialAuth.bind(null, 'facebook'),
82 | authViaGithub: socialAuth.bind(null, 'github'),
83 | authViaGoogle: socialAuth.bind(null, 'google')
84 | })
85 |
86 | export default connect(SocialButtons)
87 |
--------------------------------------------------------------------------------
/src/components/shared/auth/components/style.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
3 |
156 |
157 | )
158 |
--------------------------------------------------------------------------------
/src/components/shared/auth/forgot-password.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import Button from '../../ui/button'
3 | import Input from '../../ui/input'
4 | import InputList from '../../ui/input-list'
5 | import Style from './components/style'
6 |
7 | const ForgotPasswordForm = ({
8 | email, password, toggleSignInModal, resetPassword, messages
9 | }) => (
10 |
36 | )
37 |
38 | ForgotPasswordForm.init = ({
39 | stores: {messages},
40 | services: {
41 | ui: {toggleModal},
42 | auth: {resetPassword}
43 | },
44 | form: {fields: {email}, submit}
45 | }) => ({
46 | email,
47 | toggleModal,
48 | messages,
49 | toggleSignInModal: () => toggleModal('signin'),
50 | resetPassword: (e) => submit(e, resetPassword)
51 | })
52 |
53 | ForgotPasswordForm.form = {
54 | formName: 'ForgotPasswordForm',
55 | fields: {
56 | email: {
57 | type: 'email',
58 | placeholder: 'E-mail address...'
59 | }
60 | }
61 | }
62 |
63 | export default connect(ForgotPasswordForm)
64 |
--------------------------------------------------------------------------------
/src/components/shared/auth/set-password.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import Button from '../../ui/button'
3 | import Input from '../../ui/input'
4 | import InputList from '../../ui/input-list'
5 | import Style from './components/style'
6 |
7 | const SetPasswordForm = ({
8 | fields, setPassword, messages
9 | }) => (
10 |
32 | )
33 |
34 | SetPasswordForm.init = ({
35 | stores: {messages, ui},
36 | services: {
37 | ui: {toggleModal},
38 | auth: {setPassword}
39 | },
40 | form: {fields, submit}
41 | }) => ({
42 | fields,
43 | toggleModal,
44 | messages,
45 | data: ui.modalData,
46 | toggleSignInModal: () => toggleModal('signin'),
47 | setPassword: (e) => submit(e, setPassword)
48 | })
49 |
50 | SetPasswordForm.form = {
51 | formName: 'SetPasswordForm',
52 | fields: ({stores: {ui: {modalData}}}) => {
53 | return {
54 | email: {
55 | type: 'email',
56 | placeholder: 'E-mail address...',
57 | readOnly: true,
58 | disabled: true,
59 | value: modalData.email
60 | },
61 | account_key: {
62 | value: modalData.account_key
63 | },
64 | password: {
65 | type: 'password',
66 | placeholder: 'Password...'
67 | }
68 | }
69 | }
70 | }
71 |
72 | export default connect(SetPasswordForm)
73 |
--------------------------------------------------------------------------------
/src/components/shared/auth/signin.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import Button from '../../ui/button'
3 | import Input from '../../ui/input'
4 | import InputList from '../../ui/input-list'
5 | import Style from './components/style'
6 | import SocialButtons from './components/social-buttons'
7 |
8 | const SignInForm = ({
9 | email, password, toggleModal, login, messages
10 | }) => (
11 |
12 |
13 |
14 |
15 |
16 |
25 |
26 |
27 | By signing in you confirm that you accept our
Terms of Use
28 |
29 |
30 |
38 |
39 |
40 |
41 |
42 | )
43 |
44 | SignInForm.init = ({
45 | stores: {messages},
46 | services: {
47 | ui: {toggleModal},
48 | auth: {login}
49 | },
50 | form: {fields: {email, password}, submit}
51 | }) => ({
52 | email,
53 | password,
54 | toggleModal,
55 | messages,
56 | login: (e) => submit(e, data => {
57 | window.analytics.track('Sign in Website Confirmed')
58 | login(data)
59 | })
60 | })
61 |
62 | SignInForm.form = {
63 | formName: 'SignInForm',
64 | fields: {
65 | email: {
66 | type: 'email',
67 | placeholder: 'E-mail address...'
68 | },
69 | password: {
70 | type: 'password',
71 | placeholder: 'Password...'
72 | }
73 | }
74 | }
75 |
76 | export default connect(SignInForm)
77 |
--------------------------------------------------------------------------------
/src/components/shared/auth/signup.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import {action} from 'mobx'
3 | import Button from '../../ui/button'
4 | import Input from '../../ui/input'
5 | import InputList from '../../ui/input-list'
6 | import Style from './components/style'
7 | import SocialButtons from './components/social-buttons'
8 | import Quotes from './components/quotes'
9 | import Footer from './components/footer'
10 |
11 | const SignUpForm = ({
12 | email, password, toggleModal, register, messages, withSocialButtons,
13 | withQuotes, ui
14 | }) => (
15 |
58 | )
59 |
60 | SignUpForm.init = ({
61 | stores: {messages, ui},
62 | services: {
63 | ui: {toggleModal},
64 | auth: {register}
65 | },
66 | form: {fields: {email, password}, submit},
67 | withSocialButtons,
68 | withQuotes
69 | }) => ({
70 | email,
71 | password,
72 | toggleModal,
73 | messages,
74 | withSocialButtons,
75 | withQuotes,
76 | ui,
77 | register: (e) => submit(e, data => {
78 | if (email.value !== '' && password.value !== '') {
79 | window.analytics.track('Sign up Website Confirmed')
80 | }
81 | register(data)
82 | })
83 | })
84 |
85 | SignUpForm.form = {
86 | formName: 'SignUpForm',
87 | fields: {
88 | email: {
89 | type: 'email',
90 | placeholder: 'E-mail address...'
91 | },
92 | password: {
93 | type: 'password',
94 | placeholder: 'Password...'
95 | }
96 | }
97 | }
98 |
99 | export default connect(SignUpForm)
100 |
--------------------------------------------------------------------------------
/src/components/shared/cli.js:
--------------------------------------------------------------------------------
1 | const CLI = () => (
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Syncano CLI
9 |
10 |
11 | $ npx syncano-cli hot
12 |
13 | 🔥 Hot deploy started (Hit Ctrl-C to stop)
14 |
15 |
16 | project synced: 14:38:54 1337 ms
17 |
18 |
19 | socket synced: 14:38:54
20 | openweathermap
21 | 3545 ms
22 |
23 |
24 | socket synced: 14:38:59
25 | messenger-bot
26 | 4144 ms
27 |
28 |
29 | socket synced: 14:39:01
30 | twitter-bot
31 | 4238 ms
32 |
33 |
34 | socket synced: 14:39:01
35 | analytics
36 | 4238 ms
37 |
38 |
39 | socket synced: 14:39:01
40 | mailgun
41 | 2297 ms
42 |
43 | {/* TODO: Add animation */}
44 | waiting...
45 |
46 |
47 |
149 |
150 | )
151 |
152 | export default CLI
153 |
--------------------------------------------------------------------------------
/src/components/shared/cta.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import Button from '../ui/button'
3 | import Hexagon from '../ui/hexagon'
4 | import {MatchAsGuest} from '../ui/router'
5 |
6 | const CTA = ({toggleSignUpModal}) => (
7 | (
8 |
9 |
Ready to get started?
10 |
Try Syncano for free for 30 days. No credit card required.
11 |
12 |
13 | Start free trial
14 | {/* TODO: Will be replaced with modal form */}
15 | {/* hj('trigger', 'notConvinced')}>I'm not convinced */}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
75 |
76 | )} />
77 | )
78 |
79 | CTA.init = ({
80 | services: {ui: {toggleModal}}
81 | }) => ({
82 | toggleSignUpModal: e => {
83 | window.analytics.track('Sign up Website')
84 | toggleModal('signup')
85 | }
86 | })
87 |
88 | export default connect(CTA)
89 |
--------------------------------------------------------------------------------
/src/components/shared/header/user-nav.js:
--------------------------------------------------------------------------------
1 | import Nav from '../../ui/nav'
2 | import {MatchAsGuest, MatchAsMember} from '../../ui/router'
3 |
4 | const UserNav = ({theme, toggleSignUp, toggleSignIn, pageStatus}) => (
5 |
125 | )
126 |
127 | export default UserNav
128 |
--------------------------------------------------------------------------------
/src/components/shared/youTube.js:
--------------------------------------------------------------------------------
1 | /*
2 | Usage:
3 |
4 |
10 | */
11 |
12 | const YouTube = ({ video, autoplay, rel, showInfo }) => (
13 | VIDEO
21 | );
22 |
23 | export default YouTube;
24 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.js:
--------------------------------------------------------------------------------
1 | import md5 from 'md5'
2 |
3 | const GRAVATAR_URL = 'https://gravatar.com/avatar/'
4 |
5 | const Avatar = ({src, srcSet, size, email}) => {
6 | const gravatar = src || `${GRAVATAR_URL}${md5(email)}?s=${size}&d=mm`
7 |
8 | return (
9 |
10 |
18 |
19 |
29 |
30 | )
31 | }
32 |
33 | export default Avatar
34 |
--------------------------------------------------------------------------------
/src/components/ui/button.js:
--------------------------------------------------------------------------------
1 | const Button = ({
2 | children,
3 | primary, secondary, outline, github, google, facebook, full, square,
4 | ...props
5 | }) => (
6 |
19 | {children}
20 |
163 |
164 | )
165 |
166 | export default Button
167 |
--------------------------------------------------------------------------------
/src/components/ui/grid.js:
--------------------------------------------------------------------------------
1 | const Nav = ({children, wrap}) => (
2 |
6 | {children}
7 |
8 |
22 |
23 | )
24 |
25 | export default Nav
26 |
--------------------------------------------------------------------------------
/src/components/ui/head.js:
--------------------------------------------------------------------------------
1 | import Head from 'zefir/head'
2 | import React from 'react'
3 |
4 | const {OPTIMIZELY_KEY} = process.env
5 | const isDesktop = window.innerWidth > 768
6 | const robotoFontLink =
9 | const ptMonoFontLink =
12 |
13 | export default ({children}) => (
14 |
15 |
16 |
17 | {children}
18 | { isDesktop && ptMonoFontLink }
19 | { isDesktop && robotoFontLink }
20 | {React.Children.map(children, item => {
21 | if (item.type === 'title') {
22 | const title = item.props.children
23 | const page = title.indexOf('-') >= 0 ? title.split('-')[1] : title
24 |
25 | window.analytics.page(`Website -${page}`, {
26 | path: window.location.hash
27 | })
28 | }
29 | })}
30 |
31 | )
32 |
--------------------------------------------------------------------------------
/src/components/ui/hexagon.js:
--------------------------------------------------------------------------------
1 | import Hex from 'react-hexagon'
2 |
3 | const Hexagon = ({width, fill, opacity, children, style, position}) => {
4 | let positionStyle = {}
5 | const element = (
6 |
16 | {children}
17 |
18 | )
19 |
20 | if (position) {
21 | positionStyle = {
22 | position: 'absolute',
23 | zIndex: 10,
24 | ...position
25 | }
26 | }
27 |
28 | if (width) {
29 | return (
30 |
35 | {element}
36 |
37 | )
38 | }
39 |
40 | return element
41 | }
42 |
43 | export default Hexagon
44 |
--------------------------------------------------------------------------------
/src/components/ui/input-list.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const InputList = ({children, errors = {}}) => {
4 | const errorKeys = Object.keys(errors)
5 | const matchedErrors = React.Children.map(children, input => input.props.name)
6 | const unmatchedErors = errorKeys.filter(err => matchedErrors.indexOf(err) < 0)
7 |
8 | return (
9 |
10 | {React.Children.map(children, input => (
11 |
12 | {input}
13 |
14 | {errors[input.props.name] && (
15 |
{errors[input.props.name]}
16 | )}
17 |
18 | ))}
19 |
20 | {unmatchedErors.map(err => (
21 |
{errors[err]}
22 | ))}
23 |
24 |
35 |
36 | )
37 | }
38 |
39 | export default InputList
40 |
--------------------------------------------------------------------------------
/src/components/ui/input.js:
--------------------------------------------------------------------------------
1 | const Input = ({full, white, clearable, handleClear, ...props}) => (
2 |
8 |
9 |
10 | {clearable && props.value && (
11 |
12 | )}
13 |
54 |
55 | )
56 |
57 | export default Input
58 |
--------------------------------------------------------------------------------
/src/components/ui/link.js:
--------------------------------------------------------------------------------
1 | import {Link} from 'zefir/router'
2 |
3 | export default props => (
4 |
5 |
6 |
7 |
16 |
17 | )
18 |
--------------------------------------------------------------------------------
/src/components/ui/loader.js:
--------------------------------------------------------------------------------
1 | const Loader = () => (
2 |
3 |
20 |
21 | )
22 |
23 | export default Loader
24 |
--------------------------------------------------------------------------------
/src/components/ui/logo/index.js:
--------------------------------------------------------------------------------
1 | import Logo from './logo.svg'
2 |
3 | export default Logo
4 |
--------------------------------------------------------------------------------
/src/components/ui/logo/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/ui/markdown.js:
--------------------------------------------------------------------------------
1 | import {MarkdownPreview} from 'react-marked-markdown'
2 |
3 | const Markdown = ({content}) => (
4 |
5 |
6 |
7 |
154 |
155 | )
156 |
157 | export default Markdown
158 |
--------------------------------------------------------------------------------
/src/components/ui/menu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Menu = ({children}) => (
4 |
5 | {React.Children.map(children, item => (
6 | {item}
7 | ))}
8 |
9 |
68 |
69 | )
70 |
71 | export default Menu
72 |
--------------------------------------------------------------------------------
/src/components/ui/nav-link.js:
--------------------------------------------------------------------------------
1 | import {NavLink} from 'zefir/router'
2 |
3 | export default props => (
4 |
5 |
6 |
7 |
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/src/components/ui/nav.js:
--------------------------------------------------------------------------------
1 | import {Children} from 'react'
2 |
3 | const Nav = ({children, stack}) => (
4 |
5 |
6 | {Children.map(children, Item => (
7 |
8 | {Item}
9 |
10 | ))}
11 |
12 |
13 |
23 |
24 | )
25 |
26 | export default Nav
27 |
--------------------------------------------------------------------------------
/src/components/ui/router.js:
--------------------------------------------------------------------------------
1 | import {Match, Switch, defineMatch} from 'zefir/router'
2 |
3 | const isGuest = ({stores}) => !stores.auth.isLoggedIn
4 | const MatchAsGuest = defineMatch(isGuest)
5 |
6 | const isMember = ({stores}) => stores.auth.isLoggedIn
7 | const MatchAsMember = defineMatch(isMember)
8 |
9 | export {Match, MatchAsGuest, MatchAsMember, Switch}
10 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-manager.js:
--------------------------------------------------------------------------------
1 | import {Component} from 'react'
2 |
3 | class ScrollManager extends Component {
4 | componentDidMount (prevProps) {
5 | const isLocal = window.location.hostname === 'localhost'
6 |
7 | if (!isLocal) {
8 | window.scrollTo(0, 0)
9 | }
10 | }
11 |
12 | render () {
13 | return null
14 | }
15 | }
16 |
17 | export default ScrollManager
18 |
--------------------------------------------------------------------------------
/src/components/ui/sidebar.js:
--------------------------------------------------------------------------------
1 | const Sidebar = ({children}) => (
2 |
3 | {children}
4 |
5 |
15 |
16 | )
17 |
18 | export default Sidebar
19 |
--------------------------------------------------------------------------------
/src/components/ui/text.js:
--------------------------------------------------------------------------------
1 | const Text = ({children}) => (
2 |
3 | {children}
4 |
123 |
124 | )
125 |
126 | export default Text
127 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Syncano - Accelerated Software Development
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import {Match, Switch} from './components/ui/router'
2 |
3 | import LandingView from './views/landing'
4 | import FeaturesView from './views/features'
5 | import PricingView from './views/pricing'
6 | import FaqView from './views/faq'
7 | import SlackInviteView from './views/slack-invite'
8 | import ToSView from './views/terms/terms-of-service'
9 | import PrivacyPolicyView from './views/terms/privacy-policy'
10 | import AccepatbleUsePolicyView from './views/terms/acceptable-use-policy'
11 | import SyncanoPerformanceInsightsView from './views/terms/syncano-performance-insights'
12 | import GeneralTermsView from './views/terms/general-terms'
13 | import SocketsView from './views/sockets'
14 | import SignUpView from './views/signup'
15 | import SignInView from './views/signin'
16 | import MissingView from './views/missing'
17 | import EnterpriseView from './views/enterprise'
18 |
19 | const Routes = () => (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | )
38 |
39 | Routes.init = ({
40 | services: {auth, ui}
41 | }) => {
42 | auth.rebuildSession()
43 | ui.addSegment(process.env.ANALYTICS_WRITE_KEY)
44 | ui.fetchServicesStatus()
45 | }
46 |
47 | export default Routes
48 |
--------------------------------------------------------------------------------
/src/services/auth.service.js:
--------------------------------------------------------------------------------
1 | import hello from 'hellojs'
2 | import Cookies from 'js-cookie'
3 | import {action} from 'mobx'
4 |
5 | const LOGIN_URL = `${process.env.API_URL}/v1.1/account/auth/`
6 | const REGISTER_URL = `${process.env.API_URL}/v1.1/account/register/`
7 | const SET_PASSWORD_URL = `${process.env.API_URL}/v1.1/account/password/set/`
8 | const FORGOT_PASSWORD_URL = `${process.env.API_URL}/v1.1/account/password/reset/`
9 |
10 | export default class Auth {
11 | constructor (props) {
12 | this.initHello()
13 | }
14 |
15 | @action.bound rebuildSession () {
16 | this.store.isLoggedIn = Boolean(Cookies.get('logged_in'))
17 | }
18 |
19 | @action.bound async login (credentials) {
20 | const {request} = this.services
21 | const {messages} = this.stores
22 |
23 | messages.delete('auth.login')
24 |
25 | try {
26 | const res = await request.post(LOGIN_URL, credentials)
27 |
28 | this.redirectToDashboard(res.data.account_key)
29 | } catch (err) {
30 | const {data} = err.response
31 |
32 | messages.set('auth.login', data)
33 | }
34 | }
35 |
36 | @action.bound async register (credentials) {
37 | const {request} = this.services
38 | const {messages} = this.stores
39 |
40 | messages.delete('auth.register')
41 |
42 | try {
43 | const res = await request.post(REGISTER_URL, credentials)
44 |
45 | this.redirectToDashboard(res.data.account_key, true)
46 | } catch (err) {
47 | const {data} = err.response
48 |
49 | messages.set('auth.register', data)
50 | }
51 | }
52 |
53 | @action.bound socialAuth (network) {
54 | const {messages} = this.stores
55 | const {request, ui} = this.services
56 |
57 | this.hello(network).login(async social => {
58 | const {access_token} = social.authResponse
59 |
60 | if (social.network === 'google') {
61 | social.network = 'google-oauth2'
62 | }
63 |
64 | try {
65 | const res = await request.post(`${LOGIN_URL}${social.network}/`, {access_token})
66 |
67 | if (res.data.has_password) {
68 | this.redirectToDashboard(res.data.account_key, res.data.created)
69 | } else {
70 | ui.toggleModal('set-password', res.data)
71 | }
72 | } catch (err) {
73 | console.log(err)
74 | messages.set('auth.social', err.message)
75 | }
76 | }, err => {
77 | messages.set('auth.social', err)
78 | })
79 | }
80 |
81 | @action.bound async setPassword ({password, account_key}) {
82 | const {request} = this.services
83 | const {messages} = this.stores
84 |
85 | messages.delete('auth.set-password.errors')
86 | messages.delete('auth.set-password.success')
87 | messages.set('auth.set-password.pending')
88 |
89 | try {
90 | await request.post(SET_PASSWORD_URL, {password}, {
91 | headers: {
92 | 'X-API-KEY': account_key
93 | }
94 | })
95 |
96 | messages.set('auth.set-password.success', 'Check your inbox.')
97 | } catch (err) {
98 | const {data} = err.response
99 |
100 | messages.set('auth.set-password.errors', data)
101 | }
102 |
103 | messages.delete('auth.set-password.pending')
104 | }
105 |
106 | @action.bound async resetPassword (credentials) {
107 | const {request} = this.services
108 | const {messages} = this.stores
109 |
110 | messages.delete('auth.forgot-password.errors')
111 | messages.delete('auth.forgot-password.success')
112 | messages.set('auth.forgot-password.pending')
113 |
114 | try {
115 | await request.post(FORGOT_PASSWORD_URL, credentials)
116 |
117 | messages.set('auth.forgot-password.success', 'Check your inbox.')
118 | } catch (err) {
119 | const {data} = err.response
120 |
121 | messages.set('auth.forgot-password.errors', data)
122 | }
123 |
124 | messages.delete('auth.forgot-password.pending')
125 | }
126 |
127 | redirectToDashboard = (token, signUpMode = false) => {
128 | let redirectUrl = `${process.env.DASHBOARD_URL}/#/login?token=${token}`
129 |
130 | if (signUpMode) {
131 | redirectUrl += `&signUpMode=${signUpMode}`
132 | }
133 |
134 | window.location.href = redirectUrl
135 | }
136 |
137 | initHello () {
138 | const credentials = {
139 | facebook: process.env.HELLO_FACEBOOK,
140 | google: process.env.HELLO_GOOGLE,
141 | github: process.env.HELLO_GITHUB
142 | }
143 |
144 | const options = {
145 | scope: 'email',
146 | redirect_uri: process.env.HELLO_REDIRECT_URI
147 | }
148 |
149 | hello.init(credentials, options)
150 |
151 | this.hello = hello.bind(hello)
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/services/request.service.js:
--------------------------------------------------------------------------------
1 | import request from 'axios'
2 |
3 | export default () => request
4 |
--------------------------------------------------------------------------------
/src/services/ui.service.js:
--------------------------------------------------------------------------------
1 | import {action} from 'mobx'
2 | import _ from 'lodash'
3 |
4 | const STATUS_PAGE_URL = 'https://6l1kzwgr7t06.statuspage.io/api/v2/status.json'
5 | const STATUS_PAGE_TIMEOUT = 5000
6 |
7 | export default class UI {
8 | @action.bound toggleFlag (flag, value) {
9 | if (this.store.flags.has(flag)) {
10 | this.store.flags.delete(flag)
11 | } else {
12 | this.store.flags.set(flag, value)
13 | }
14 | }
15 |
16 | @action.bound toggleModal (name, data) {
17 | this.store.modalData = data
18 | this.store.modal = this.store.modal === name ? null : name
19 |
20 | window.analytics.page(`Website - ${_.upperFirst(name)}`, {
21 | path: `#/${name}`,
22 | title: `Website - ${_.upperFirst(name)}`
23 | })
24 | }
25 |
26 | @action.bound async fetchServicesStatus () {
27 | try {
28 | const res = await this.services.request(STATUS_PAGE_URL)
29 | const {indicator, description} = res.data.status
30 |
31 | this.store.pageStatus.indicator = indicator
32 | this.store.pageStatus.description = description
33 |
34 | setTimeout(this.fetchServicesStatus, STATUS_PAGE_TIMEOUT)
35 | } catch (e) {}
36 | }
37 |
38 | addSegment (writeKey) {
39 | const isLocal = window.location.hostname === 'localhost'
40 |
41 | if (isLocal) {
42 | window.analytics = {
43 | page: () => {},
44 | track: () => {}
45 | }
46 |
47 | return
48 | }
49 |
50 | if (!writeKey) {
51 | return
52 | }
53 |
54 | // eslint-disable-next-line
55 | var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/static/img/enterprise/check-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/static/img/enterprise/code-1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/static/img/enterprise/cursor-double-click.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/static/img/enterprise/dnb_logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/enterprise/dnb_logo.jpg
--------------------------------------------------------------------------------
/src/static/img/enterprise/graduation-cap.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/static/img/enterprise/server-check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/static/img/enterprise/window-code.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/static/img/features/clouds.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/features/clouds.jpg
--------------------------------------------------------------------------------
/src/static/img/features/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/features/dashboard.png
--------------------------------------------------------------------------------
/src/static/img/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/og-image.png
--------------------------------------------------------------------------------
/src/static/img/slack-invite/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/slack-invite/og-image.png
--------------------------------------------------------------------------------
/src/static/img/slack-invite/syncano-slack.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/static/img/slack-invite/twitter-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/slack-invite/twitter-image.png
--------------------------------------------------------------------------------
/src/static/img/syncano-logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/syncano-logo-dark.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/artur-czmiel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/artur-czmiel.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/artur-czmiel@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/artur-czmiel@2x.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/halvor-lande.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/halvor-lande.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/halvor-lande@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/halvor-lande@2x.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/ingar-bentzen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/ingar-bentzen.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/ingar-bentzen@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/ingar-bentzen@2x.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/nikolai-fasting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/nikolai-fasting.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/nikolai-fasting@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/nikolai-fasting@2x.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/stale-husby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/stale-husby.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/stale-husby@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/stale-husby@2x.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/sven-sunde.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/sven-sunde.png
--------------------------------------------------------------------------------
/src/static/img/testimonials/sven-sunde@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Syncano/website/0fb965d92f54232cde80ef8a92f4f8414241c76e/src/static/img/testimonials/sven-sunde@2x.png
--------------------------------------------------------------------------------
/src/static/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://syncano.io/#/
4 | https://syncano.io/#/features
5 | https://syncano.io/#/pricing
6 | https://syncano.io/#/faq
7 | https://syncano.io/#/sockets
8 | https://syncano.io/#/signup
9 | https://syncano.io/#/signin
10 | https://syncano.io/#/slack-invite
11 | https://syncano.io/#/terms-of-service
12 | https://syncano.io/#/privacy-policy
13 | https://syncano.io/#/acceptable-use-policy
14 | https://syncano.io/#/syncano-performance-insights
15 | https://syncano.io/#/general-terms
16 | https://syncano.io/#/enterprise
17 |
18 |
--------------------------------------------------------------------------------
/src/stores/auth.store.js:
--------------------------------------------------------------------------------
1 | import {observable} from 'mobx'
2 |
3 | export default {
4 | @observable isLoggedIn: false
5 | }
6 |
--------------------------------------------------------------------------------
/src/stores/messages.store.js:
--------------------------------------------------------------------------------
1 | import {observable} from 'mobx'
2 |
3 | export default observable.map([])
4 |
--------------------------------------------------------------------------------
/src/stores/pending.store.js:
--------------------------------------------------------------------------------
1 | import {observable} from 'mobx'
2 |
3 | export default observable.map([])
4 |
--------------------------------------------------------------------------------
/src/stores/ui.store.js:
--------------------------------------------------------------------------------
1 | import {observable} from 'mobx'
2 |
3 | export default {
4 | @observable pageStatus: {
5 | description: null,
6 | indicator: null
7 | },
8 | @observable modal: null,
9 | flags: observable.map()
10 | }
11 |
--------------------------------------------------------------------------------
/src/views/enterprise/components/book-meeting.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import {connect} from 'zefir/utils'
3 | import Button from '../../../components/ui/button'
4 | import Input from '../../../components/ui/input'
5 | import InputList from '../../../components/ui/input-list'
6 | import Style from '../../../components/shared/auth/components/style'
7 | import Quotes from '../../../components/shared/auth/components/quotes'
8 |
9 | const addIntercomLead = ({ email, env, customAttributes = {} }) => {
10 | axios.post('https://dry-wave-5153.syncano.space/intercom-add-lead/add_lead/', {
11 | env,
12 | email,
13 | custom_attributes: customAttributes
14 | });
15 | }
16 |
17 | const BookMeetingForm = ({
18 | email, name, company, addLead
19 | }) => (
20 |
21 |
22 |
23 |
24 |
36 |
37 |
38 | )
39 |
40 | BookMeetingForm.init = ({
41 | services: {
42 | ui: {toggleModal}
43 | },
44 | form: {fields: {name, email, company}}
45 | }) => ({
46 | email,
47 | name,
48 | company,
49 | toggleModal,
50 | addLead: () => {
51 | const env = process.env.API_URL === 'https://api.syncano.io/' ? 'production' : 'staging'
52 | const emailValue = email.value
53 | const nameValue = name.value
54 | const companyValue = company.value
55 |
56 | addIntercomLead({
57 | email: emailValue,
58 | env,
59 | customAttributes:
60 | {
61 | enterprise: true,
62 | name: nameValue,
63 | company: companyValue
64 | }
65 | })
66 |
67 | window.analytics.track('Book Meeting')
68 | toggleModal('book-meeting')
69 | toggleModal('booking-confirmed')
70 | }
71 | })
72 |
73 | BookMeetingForm.form = {
74 | formName: 'BookMeetingForm',
75 | fields: {
76 | email: {
77 | type: 'email',
78 | placeholder: 'E-mail address...'
79 | },
80 | name: {
81 | type: 'name',
82 | placeholder: 'Full name...'
83 | },
84 | company: {
85 | type: 'company',
86 | placeholder: 'Company name...'
87 | }
88 | }
89 | }
90 |
91 | export default connect(BookMeetingForm)
92 |
--------------------------------------------------------------------------------
/src/views/enterprise/components/enterpriseComponents.js:
--------------------------------------------------------------------------------
1 | import Logo from '../../../components/ui/logo'
2 | import Hexagon from '../../../components/ui/hexagon'
3 |
4 | const EnterpriseComponents = () => (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Cloud Tech
12 |
13 |
14 | Iterate faster with less resources thanks to the developer friendly Syncano Automation SDK
15 | Reuse your code and connect legacy IT with Syncano Sockets
16 | Scale instantaneously to thousands of users with Syncano Cloud OS
17 |
18 | Don’t reinvent the wheel.
19 | Browse Syncano Socket Registry and reuse solutions created by the community
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
Crowdsourced Development
29 |
30 |
31 |
32 | Embrace the digital transformation with the power of crowdsourcing.
33 | Get access to a pool of 60.000 Syncano Developers
34 |
35 | Launch MVPs faster with smaller teams thanks to our innovative work methodologies
36 | Have our innovation specialists guide you through the process
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
124 |
125 | )
126 |
127 | export default EnterpriseComponents
128 |
--------------------------------------------------------------------------------
/src/views/enterprise/components/enterpriseCta.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import Button from '../../../components/ui/button'
3 | import Hexagon from '../../../components/ui/hexagon'
4 | import {MatchAsGuest} from '../../../components/ui/router'
5 |
6 | const EnterpriseCTA = ({toggleBookMeetingModal}) => (
7 | (
9 |
10 |
Let’s talk
11 |
12 | We’d love to have a chat about how your organization can leverage the power of Syncano
13 |
14 |
15 |
16 | Book a meeting now
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
66 |
67 | )}
68 | />
69 | )
70 |
71 | EnterpriseCTA.init = ({
72 | services: {ui: {toggleModal}}
73 | }) => ({
74 | toggleBookMeetingModal: () => {
75 | window.analytics.track('Booking Form Opened')
76 | toggleModal('book-meeting')
77 | }
78 | })
79 |
80 | export default connect(EnterpriseCTA)
81 |
--------------------------------------------------------------------------------
/src/views/enterprise/components/enterpriseFeatures.js:
--------------------------------------------------------------------------------
1 | import Header from '../../../components/shared/header'
2 | import IconServer from '../../../static/img/enterprise/server-check.svg'
3 | import IconIntegrations from '../../../static/img/enterprise/code-1.svg'
4 | import IconDevelopment from '../../../static/img/enterprise/window-code.svg'
5 | import IconTraining from '../../../static/img/enterprise/graduation-cap.svg'
6 | import Hexagon from '../../../components/ui/hexagon'
7 |
8 | const EnterpriseFeatures = () => (
9 |
10 |
11 |
Get access to exclusive enterprise features
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Self-hosted
20 |
21 |
22 | Host Syncano on your infrastructure and get maximum control and security
23 | combined with the scalability of your solution.
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Custom integrations
32 |
33 |
34 | Easily connect your existing API’s and databases with Syncano through Sockets
35 | - scripts that execute code in the cloud and make it simple to write any integration.
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | Development
44 |
45 |
46 | No resources? Get access to our crowd of developers who already use Syncano.
47 | They will help you build your next killer service on request.
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Support & Training
56 |
57 |
58 | Receive first-class training and support from a dedicated team of Syncano specialists working for you on-site.
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
150 |
151 | )
152 |
153 | export default EnterpriseFeatures
154 |
--------------------------------------------------------------------------------
/src/views/enterprise/components/enterpriseGraph.js:
--------------------------------------------------------------------------------
1 | import IconTransformation from '../../../static/img/enterprise/transformation.svg'
2 | import Hexagon from '../../../components/ui/hexagon'
3 |
4 |
5 | const EnterpriseGraph = () => (
6 |
7 |
8 |
9 |
Lead the digital transformation and become a disruptor in your market.
10 | Leave the hard part to Syncano.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
77 |
78 | )
79 |
80 | export default EnterpriseGraph
81 |
--------------------------------------------------------------------------------
/src/views/enterprise/index.js:
--------------------------------------------------------------------------------
1 | import {Match} from '../../components/ui/router'
2 | import Head from '../../components/ui/head'
3 | import Page from '../../components/ui/page'
4 | import Footer from '../../components/shared/footer'
5 | import Header from '../../components/shared/header'
6 | import EnterpriseCTA from './components/enterpriseCta'
7 | import EnterpriseTestimonials from './components/enterpriseTestimonials'
8 | import EnterpriseFeatures from './components/enterpriseFeatures'
9 | import EnterpriseComponents from './components/enterpriseComponents'
10 | import EnterpriseGraph from './components/enterpriseGraph'
11 | import Hexagon from '../../components/ui/hexagon'
12 |
13 | const Enterprise = ({
14 | router: {history: {push}}
15 | }) => (
16 |
17 |
18 | Syncano Scalable Community
19 | Tomorrow’s market is not won with yesterday’s services. Innovate at the speed of a startup.
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Our customers and partners include
40 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
93 |
94 | )
95 |
96 | Enterprise.init = ({
97 | router
98 | }) => ({router})
99 |
100 | export default Enterprise
101 |
--------------------------------------------------------------------------------
/src/views/faq.js:
--------------------------------------------------------------------------------
1 | import Footer from '../components/shared/footer'
2 | import Header from '../components/shared/header'
3 | import CTA from '../components/shared/cta'
4 | import Head from '../components/ui/head'
5 | import Page from '../components/ui/page'
6 | import Text from '../components/ui/text'
7 | import Link from '../components/ui/link'
8 |
9 | const Landing = () => (
10 |
11 |
12 | Syncano
13 |
14 |
15 |
19 |
20 |
21 |
22 | How is Syncano different from other cloud backend products?
23 | Syncano has been created with developers in mind; built for engineers by engineers. During the development of the latest version, we carefully listened to the feedback and learned our lessons. The outcome is a tool that seamlessly integrates with your workflow and takes it to the next level. With Syncano, working with the backend feels like http://locallhost.
24 |
25 | How does your pricing model work?
26 | Operations on Syncano are based on the number of API calls and the time it takes to execute the backend script code inside of the sockets you use. We have monthly pricing plans that include a fixed amount of API calls and script execution time. The basic plan of USD 25 includes 1 million API calls and 270k script seconds, which should usually cover around 1000 daily users making several requests through your frontend.
27 |
28 | What happens if I exceed my usage?
29 | Your application will continue to work without any interruptions, regardless of the usage of the backend. If you exceed your usage, overage fees will appear as a separate bill at the end of the month, which is our billing period. You can change your plan at any time and set limits to notify you in the event of getting close to exceeding your current plan.
30 |
31 | What will happen if my app gets popular and attracts millions of users?
32 | Will be as excited as you! You don’t need to worry about scaling issues, as our infrastructure will scale along with the load of your users. We have a proven track record of handling millions of API requests daily.
33 |
34 | Where are the libraries for iOS and Android?
35 | Syncano is currently in beta and we provide a client library for JavaScript. We are also working on the iOS, Android and Unity libraries and they will be released very soon. Ask us on Slack to get the ETAs.
36 |
37 | How can I get support?
38 | You can join our Slack channel for community support, talk to us via the messenger in the Dashboard after you log in, or simply send us an email . You can also browse through our documentation.
39 |
40 |
41 |
42 |
43 |
44 |
45 |
54 |
55 | )
56 |
57 | export default Landing
58 |
--------------------------------------------------------------------------------
/src/views/features/components/content-nav/content-nav-item.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import {scrollTo} from '../../helpers'
3 | import {Component} from 'react'
4 | import Icon0 from './icon-0.svg'
5 | import Icon1 from './icon-1.svg'
6 | import Icon2 from './icon-2.svg'
7 | import Icon3 from './icon-3.svg'
8 |
9 | class ContentNavItem extends Component {
10 | renderIcon(section) {
11 | const contentNavIcons = [Icon0, Icon1, Icon2, Icon3]
12 |
13 | return contentNavIcons[section]
14 | }
15 |
16 | render () {
17 | const {activeSection} = this.props.featuresPage
18 | const {text, sectionId, section } = this.props
19 | const isActive = activeSection === section ? 'is-active' : ''
20 | const Icon = this.renderIcon(section)
21 |
22 | return (
23 | { scrollTo(sectionId) }}
26 | >
27 |
31 |
32 |
100 |
101 | )
102 | }
103 | }
104 |
105 | ContentNavItem.init = ({
106 | section,
107 | sectionId,
108 | text,
109 | stores: {featuresPage}
110 | }) => ({
111 | section,
112 | sectionId,
113 | text,
114 | featuresPage
115 | })
116 |
117 | export default connect(ContentNavItem)
118 |
--------------------------------------------------------------------------------
/src/views/features/components/content-nav/icon-1.svg:
--------------------------------------------------------------------------------
1 |
2 | icn_registry
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/views/features/components/content-nav/index.js:
--------------------------------------------------------------------------------
1 | import {observer} from "mobx-react";
2 | import {scrollTo} from '../../helpers'
3 | import ContentNavItem from './content-nav-item'
4 |
5 | const ContentNav = () => (
6 |
7 |
12 |
17 |
22 |
27 |
28 |
29 |
42 |
43 | )
44 |
45 | export default ContentNav
46 |
--------------------------------------------------------------------------------
/src/views/features/components/registries-section/icon-fb.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/views/features/components/registries-section/icon-sentry.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/views/features/components/registries-section/icon-stripe.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
12 |
13 |
14 |
15 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/views/features/components/registries-section/icon-twitter.svg:
--------------------------------------------------------------------------------
1 |
2 | icn
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/features/featuresPage.service.js:
--------------------------------------------------------------------------------
1 | import {action} from 'mobx'
2 |
3 | export default class {
4 | @action.bound setActiveSection (value) {
5 | this.store.activeSection = value
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/views/features/featuresPage.store.js:
--------------------------------------------------------------------------------
1 | import {observable} from 'mobx'
2 |
3 | export default observable({
4 | activeSection: 0
5 | })
6 |
--------------------------------------------------------------------------------
/src/views/features/helpers.js:
--------------------------------------------------------------------------------
1 | export function findOffsetTop (obj) {
2 | var curtop = 0
3 |
4 | if (obj.offsetParent) {
5 | do {
6 | curtop += obj.offsetTop
7 | } while ((obj = obj.offsetParent))
8 |
9 | return [curtop - 55]
10 | }
11 | }
12 |
13 | export function scrollTo (id) {
14 | window.scroll(0, findOffsetTop(document.getElementById(id)))
15 | }
16 |
--------------------------------------------------------------------------------
/src/views/features/index.js:
--------------------------------------------------------------------------------
1 | import {Component} from 'react'
2 | import {StickyContainer, Sticky} from 'react-sticky'
3 | import {scrollTo} from './helpers'
4 |
5 | import Footer from '../../components/shared/footer'
6 | import Header from '../../components/shared/header'
7 | import CTA from '../../components/shared/cta'
8 |
9 | import Head from '../../components/ui/head'
10 | import Page from '../../components/ui/page'
11 | import Button from '../../components/ui/button'
12 |
13 | import ContentNav from './components/content-nav'
14 | import CommunitySection from './components/community-section'
15 | import AutomationSDKSection from './components/automation-sdk-section'
16 | import CloudOsSection from './components/cloud-os-section'
17 | import RegistriesSection from './components/registries-section'
18 |
19 | class Features extends Component {
20 | componentWillUnmount () {
21 | window.removeEventListener('scroll', this.handleScroll)
22 | }
23 |
24 | componentDidMount () {
25 | const {hash} = this.props
26 |
27 | if (hash) {
28 | setTimeout(() => scrollTo(hash.substr(1)), 10)
29 | }
30 |
31 | window.addEventListener('scroll', this.handleScroll)
32 | }
33 |
34 | render () {
35 | const {toggleSignUpModal, scrollToTop} = this.props
36 |
37 | return (
38 |
39 |
40 | Syncano - Product Features
41 | Syncano’s Serverless Platform and SDKs automatically assemble and deploy your backend to the cloud - all while you build your app.
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
103 |
104 | )
105 | }
106 |
107 | // calculates the top offset for each of the features section
108 | // add section ID to the sections array if a new section is added
109 | getOffsetsArray() {
110 | const sections = ['AutomationSDKSection', 'RegistriesSection', 'CloudOsSection', 'CommunitySection']
111 | let offsets = []
112 |
113 | for (var i = 0; i < sections.length; i++) {
114 | offsets.push(document.getElementById(sections[i]).offsetTop)
115 | }
116 |
117 | return offsets
118 | }
119 |
120 | handleScroll = () => {
121 | const {scrollTop} = document.body
122 | const top = scrollTop + 55
123 | const offsets = this.getOffsetsArray()
124 |
125 | for (var i = 0; i < offsets.length; i++) {
126 | const z = i + 1
127 |
128 | // if between start and end of a section, set active section
129 | if (top >= offsets[i] && top < offsets[z]) {
130 | return this.props.setActiveSection(i)
131 | } else if (top >= offsets[i] && z === offsets.length -1) {
132 | const last = offsets.length - 1
133 |
134 | this.props.setActiveSection(last)
135 | }
136 | }
137 | }
138 | }
139 |
140 | Features.init = ({
141 | router: {route: {location: {hash}}},
142 | stores: {featuresPage},
143 | services: {
144 | featuresPage: {setActiveSection},
145 | ui: {toggleModal}
146 | }
147 | }) => ({
148 | hash,
149 | featuresPage,
150 | setActiveSection,
151 | scrollToTop: () => window.scroll(0, 0),
152 | toggleSignUpModal: () => {
153 | window.analytics.track('Sign up Website')
154 |
155 | toggleModal('signup')
156 | }
157 | })
158 |
159 | export default Features
160 |
--------------------------------------------------------------------------------
/src/views/landing/sections/features/icon-cloud-os.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/views/landing/sections/features/icon-community.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/views/landing/sections/features/icon-socket-registry.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/views/landing/sections/innovation.js:
--------------------------------------------------------------------------------
1 | import Hexagon from '../../../components/ui/hexagon'
2 | import Grid from '../../../components/ui/grid'
3 |
4 | const Innovation = () => (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Syncano is a development platform for building more, faster by leveraging existing backend code. Developers can finally spend their time solving app specific challenges.
17 |
18 |
19 | Technology transforms the unthinkable into reality. The Pacemaker saves millions from the grief of loss. There’s still abundant untapped opportunity to change the world through the apps you build.
20 |
21 |
22 |
23 | {/*
24 |
25 |
Speed of innovation matters
26 |
Go build something amazing!
27 |
*/}
28 |
29 | {/*
30 |
31 |
32 |
33 |
34 |
35 |
36 |
*/}
37 |
38 |
128 |
129 | )
130 |
131 | export default Innovation
132 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/hexagons.svg:
--------------------------------------------------------------------------------
1 |
2 | Polygons_background
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/icon-fb.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/icon-firebase.svg:
--------------------------------------------------------------------------------
1 |
2 | Icon/Firebase
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/icon-google-analytics.svg:
--------------------------------------------------------------------------------
1 |
2 | Icon/Google Analytics
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/icon-segment.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/icon-sentry.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/icon-slack.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/icon-stripe.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
12 |
13 |
14 |
15 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/views/landing/sections/sockets/icon-twitter.svg:
--------------------------------------------------------------------------------
1 |
2 | icn
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/missing.js:
--------------------------------------------------------------------------------
1 | import Head from '../components/ui/head'
2 | import Page from '../components/ui/page'
3 | import Link from '../components/ui/link'
4 |
5 | const Missing = () => (
6 |
7 |
8 | Not Found - Syncano
9 |
10 |
11 |
12 |
Page was not found
13 | Back to home
14 |
15 |
16 |
31 |
32 | )
33 |
34 | export default Missing
35 |
--------------------------------------------------------------------------------
/src/views/pricing/components/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 | Line
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/pricing/components/pricing-table.js:
--------------------------------------------------------------------------------
1 | import Button from '../../../components/ui/button'
2 | import Hexagon from '../../../components/ui/hexagon'
3 | import {MatchAsGuest, MatchAsMember} from '../../../components/ui/router'
4 |
5 | const PricingTable = ({
6 | toggleSignUpModal
7 | }) => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Base price
19 |
20 | $25
21 | /mo
22 |
23 |
24 |
25 |
26 | Full access to all features
27 | 60 requests per second
28 | Unlimited storage
29 | 16 instances (apps)
30 | 8 concurrent scripts
31 | Unlimited users
32 | 1,000,000 API calls
33 | 270,000 Script seconds
34 |
35 |
36 |
37 |
(
38 |
39 |
Try free for 30 days
40 |
41 | No credit card required
42 |
43 |
44 | )} />
45 |
46 | (
47 |
48 | {
49 | window.location.href = process.env.DASHBOARD_URL
50 | }}>Go to dashboard
51 |
52 | )} />
53 |
54 |
55 |
131 |
132 | )
133 |
134 | export default PricingTable
135 |
--------------------------------------------------------------------------------
/src/views/pricing/index.js:
--------------------------------------------------------------------------------
1 | import Head from '../../components/ui/head'
2 | import Page from '../../components/ui/page'
3 | import Footer from '../../components/shared/footer'
4 | import Header from '../../components/shared/header'
5 | import CTA from '../../components/shared/cta'
6 | import FAQ from './sections/faq'
7 | import Scaling from './sections/scaling'
8 | import PricingTable from './components/pricing-table'
9 |
10 | const Pricing = ({toggleSignUpModal}) => (
11 |
12 |
13 | Syncano - Pricing
14 | Simple and flexible pricing model. 30-day money back guarantee. No contracts. Upgrade, downgrade, or cancel at any time.
15 |
16 |
17 |
18 |
19 |
We help you build technology that matters, faster
20 | 30-day money back guarantee. No contracts. Upgrade, downgrade, or cancel at any time.
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
96 |
97 | )
98 |
99 | Pricing.init = ({
100 | services: {ui: {toggleModal}}
101 | }) => ({
102 | toggleSignUpModal: () => {
103 | window.analytics.track('Sign up Website')
104 |
105 | toggleModal('signup')
106 | }
107 | })
108 |
109 | export default Pricing
110 |
--------------------------------------------------------------------------------
/src/views/pricing/sections/faq.js:
--------------------------------------------------------------------------------
1 | import Hexagon from '../../../components/ui/hexagon'
2 | import Testimonials from '../../../components/shared/testimonials'
3 |
4 | const FAQ = () => (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
What happens if I exceed my usage?
17 |
When you log into your Syncano Dashboard, you can set hard limits (absolute) and soft limits (warnings/notifications issued). If you actually exceed your usage, overage fees will appear as a separate bill at the end of the month (billing period). You can change your plan and limits at any time.
18 |
19 |
20 |
How is a Script second measured?
21 |
A Script is measured by a Webhook, Schedule, Trigger, and automation. You’re billed for each second a Script is run.
22 |
23 |
24 |
When will I be billed?
25 |
As soon as you're ready to switch to a paid account, choose your plan and simply enter your credit card. The bill for the first month is prorated but we still give you the full month’s usage. On the 1st of every month, you are billed up front for that entire month's usage. Should you exceed your usage, you can always change your plan and/or pay the overage. Your overage will appear as a separate bill at the end of that month.
26 |
27 |
28 |
Which metrics can I access?
29 |
Usage is accessible in your Dashboard. Easily track the number of API calls and Script executions per each Instance (app) on Syncano.
30 |
31 |
32 |
33 |
34 |
141 |
142 | )
143 |
144 | export default FAQ
145 |
--------------------------------------------------------------------------------
/src/views/pricing/sections/scaling.js:
--------------------------------------------------------------------------------
1 | import Hexagon from '../../../components/ui/hexagon'
2 | import Grid from '../../../components/ui/grid'
3 | import PricingEstimate from '../components/pricing-estimate'
4 |
5 | const Scaling = () => (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Syncano scales with you
13 |
14 | Your app will always get the capacity it needs - and you only pay for what you use.
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Estimated monthly cost
22 |
23 | Apps come in different shapes and sizes. As you grow, you can adjust your plan based on your needs.
24 |
25 |
26 | Pay fair overage rates when you see the number of app users skyrocket and exceed the limits of your current plan. We give a progressive discount, so bigger volume means lower unit prices.
27 |
28 |
29 |
34 |
35 |
36 |
37 |
161 |
162 | )
163 |
164 | export default Scaling
165 |
--------------------------------------------------------------------------------
/src/views/signin.js:
--------------------------------------------------------------------------------
1 | import Footer from '../components/shared/footer'
2 | import Header from '../components/shared/header'
3 | import Head from '../components/ui/head'
4 | import Page from '../components/ui/page'
5 | import SignInForm from '../components/shared/auth/signin'
6 |
7 | const SignIn = () => (
8 |
9 |
10 | Syncano
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
46 |
47 | )
48 |
49 | export default SignIn
50 |
--------------------------------------------------------------------------------
/src/views/signup.js:
--------------------------------------------------------------------------------
1 | import Footer from '../components/shared/footer'
2 | import Header from '../components/shared/header'
3 | import Head from '../components/ui/head'
4 | import Page from '../components/ui/page'
5 | import SignUpForm from '../components/shared/auth/signup'
6 |
7 | const SignUp = () => (
8 |
9 |
10 | Syncano
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
40 |
41 | )
42 |
43 | export default SignUp
44 |
--------------------------------------------------------------------------------
/src/views/slack-invite/index.js:
--------------------------------------------------------------------------------
1 | import Head from '../../components/ui/head'
2 | import Page from '../../components/ui/page'
3 | import Footer from '../../components/shared/footer'
4 | import Header from '../../components/shared/header'
5 | import SlackInviteForm from './slackInviteForm'
6 |
7 | const SlackInvite = () => (
8 |
9 |
10 | Syncano - Join Community
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
32 | )}
33 | title='Join the Community'
34 | subtitle='Join the Syncano community and chat with other developers. Share ideas, learn, build together or ask us anything!'
35 | />
36 |
37 |
38 |
39 |
40 |
41 |
42 |
72 |
73 | )
74 |
75 | export default SlackInvite
76 |
--------------------------------------------------------------------------------
/src/views/slack-invite/slackInvite.service.js:
--------------------------------------------------------------------------------
1 | import {action} from 'mobx'
2 |
3 | const INVITATION_URL = 'https://api.syncano.io/v1/instances/black-dew-3304/webhooks/p/9659a5f1693cf77733f300f1da949d3a9865f5bc/slack_invite/'
4 | const MESSAGES = {
5 | already_invited: 'Uh oh! It looks like that email address has already been invited. If you think this is an error, please send a note to support@syncano.io.',
6 | invalid_email: 'Invalid email',
7 | invite_sent: 'Woo hoo! Your invite is on it\'s way. Welcome to the community!',
8 | already_in_team: 'We can\'t send you another invite - you are already a member!'
9 | }
10 |
11 | export default class {
12 | @action.bound async sendInvitation ({email}) {
13 | const {request} = this.services
14 | const {messages} = this.stores
15 |
16 | messages.set('slack.invite.pending')
17 | messages.delete('slack.invite.success')
18 | messages.delete('slack.invite.errors')
19 |
20 | try {
21 | const res = await request.post(INVITATION_URL, {email})
22 | const {ok, error} = JSON.parse(res.data.result.stdout)
23 |
24 | if (!ok) {
25 | messages.set('slack.invite.errors', {email: MESSAGES[error]})
26 | } else {
27 | messages.set('slack.invite.success', MESSAGES.invite_sent)
28 | }
29 | } catch (e) {}
30 |
31 | messages.delete('slack.invite.pending')
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/views/slack-invite/slackInviteForm.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'zefir/utils'
2 | import Input from '../../components/ui/input'
3 | import Button from '../../components/ui/button'
4 | import InputList from '../../components/ui/input-list'
5 |
6 |
7 | const SlackInviteForm = ({ email, sendInvitation, messages }) => (
8 |
52 | )
53 |
54 | SlackInviteForm.form = {
55 | formName: 'SlackInviteFormForm',
56 | fields: {
57 | email: {
58 | required: 'true',
59 | type: 'email',
60 | placeholder: 'E-mail address'
61 | }
62 | }
63 | }
64 |
65 | SlackInviteForm.init = ({
66 | stores: { messages },
67 | services: { slackInvite: { sendInvitation } },
68 | form: { fields: { email }, submit }
69 | }) => ({
70 | messages,
71 | email,
72 | sendInvitation: (e) => submit(e, sendInvitation)
73 | })
74 |
75 | export default connect(SlackInviteForm)
76 |
--------------------------------------------------------------------------------
/src/views/sockets/components/hexagon.js:
--------------------------------------------------------------------------------
1 | import Color from 'color'
2 | import Icon from './icon.svg'
3 | import Hexagon from '../../../components/ui/hexagon'
4 |
5 | export default ({onClick, fill, title, icon}) => (
6 |
7 |
8 |
9 | {icon
10 | ?
11 | :
}
12 |
13 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
74 |
75 | )
76 |
--------------------------------------------------------------------------------
/src/views/sockets/components/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/views/sockets/components/list-item.js:
--------------------------------------------------------------------------------
1 | import get from 'lodash.get'
2 | import Hexagon from './hexagon'
3 | import Avatar from '../../../components/ui/avatar'
4 | import Link from '../../../components/ui/link'
5 |
6 | const ListItem = ({item, toggleModal}) =>
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 | {item.name}
21 |
22 |
23 |
24 | {item.description.length > 140
25 | ? `${item.description.substr(0, 140)}...`
26 | : item.description}
27 |
28 |
29 |
30 |
31 | {item.author.display_name}
32 |
33 |
34 |
35 |
36 |
98 |
99 |
100 | export default ListItem
101 |
--------------------------------------------------------------------------------
/src/views/sockets/components/list.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import Loader from '../../../components/ui/loader'
3 | import ListItem from './list-item'
4 |
5 | const List = ({store, pending, toggleDetailsModal}) => (
6 |
7 |
8 | {pending.has('sockets.fetch') ? (
9 |
10 |
11 |
12 |
Loading sockets…
13 |
14 |
15 | )
16 | : store.sortedItems.length === 0 ? (
17 | No sockets found
18 | )
19 | : store.sortedItems.map(item => (
20 |
21 | ))}
22 |
23 |
24 |
56 |
57 | )
58 |
59 | List.init = ({
60 | services: {
61 | sockets,
62 | ui: {toggleModal}
63 | },
64 | stores: {
65 | sockets: store,
66 | pending
67 | }
68 | }) => {
69 | if (store.items.length === 0) {
70 | sockets.fetch()
71 | }
72 |
73 | return ({
74 | store,
75 | pending,
76 | toggleDetailsModal: () => toggleModal('socket-details')
77 | })
78 | }
79 |
80 | export default connect(List)
81 |
--------------------------------------------------------------------------------
/src/views/sockets/components/search.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import {action} from 'mobx'
3 | import Input from '../../../components/ui/input'
4 |
5 | const Search = ({store, search, handleSearch, handleClear}) => (
6 |
7 |
8 |
9 | {store.sortedItems.length > 0 && search.value && (
10 |
11 | {store.sortedItems.length} sockets found for "{search.value} "
12 |
13 | )}
14 |
15 |
27 |
28 | )
29 |
30 | Search.init = ({
31 | form: {fields: {search}, setValue},
32 | stores: {
33 | sockets: store
34 | },
35 | services: {sockets}
36 | }) => ({
37 | search,
38 | store,
39 | handleSearch: (e) => {
40 | sockets.search(e.target.value)
41 | setValue(e)
42 | },
43 | handleClear: () => {
44 | setValue('search', '')
45 | sockets.search('')
46 | }
47 | })
48 |
49 | Search.form = {
50 | formName: 'SocketsSearchForm',
51 | fields: (props) => ({
52 | search: {
53 | type: 'search',
54 | placeholder: 'Search for Syncano Sockets'
55 | }
56 | })
57 | }
58 |
59 | export default connect(Search)
60 |
--------------------------------------------------------------------------------
/src/views/sockets/components/sidebar.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'zefir/utils'
2 | import SortList from './sort-list'
3 | import Search from './search'
4 |
5 | const Sidebar = ({store: {sortDirection, sortBy}, handleSortChange}) =>
6 |
7 |
8 |
9 |
10 |
11 | Order sockets
12 |
13 |
19 |
20 |
21 |
44 |
45 |
46 | Sidebar.init = ({stores: {sockets: store}, services: {sockets}}) => ({
47 | store,
48 | handleSortChange: name => {
49 | if (name === store.sortBy) {
50 | sockets.toggleSortDirection()
51 | }
52 |
53 | sockets.sortBy(name)
54 | },
55 | })
56 |
57 | export default connect(Sidebar)
58 |
--------------------------------------------------------------------------------
/src/views/sockets/components/sort-list.js:
--------------------------------------------------------------------------------
1 | import Menu from '../../../components/ui/menu'
2 |
3 | const SortList = ({items, active, direction, handleClick}) => (
4 |
71 | )
72 |
73 | export default SortList
74 |
--------------------------------------------------------------------------------
/src/views/sockets/index.js:
--------------------------------------------------------------------------------
1 | import {Match} from '../../components/ui/router'
2 | import Head from '../../components/ui/head'
3 | import Page from '../../components/ui/page'
4 | import Footer from '../../components/shared/footer'
5 | import Header from '../../components/shared/header'
6 | import Grid from '../../components/ui/grid'
7 | import Modal from '../../components/ui/modal'
8 | import CTA from '../../components/shared/cta'
9 | import List from './components/list'
10 | import Details from './components/details'
11 | import Search from './components/search'
12 |
13 | const Sockets = ({
14 | router: {history: {push}}
15 | }) => (
16 |
17 |
18 | Syncano - Sockets Registry
19 | Sockets Registry is a collection of reusable backend components and integrations built by the Syncano community.
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | (
41 | {
42 | push('/sockets')
43 | }}>
44 |
45 |
46 | )} />
47 |
48 |
61 |
62 | )
63 |
64 | Sockets.init = ({
65 | services: {ui: {toggleModal}},
66 | router
67 | }) => ({toggleModal, router})
68 |
69 | export default Sockets
70 |
--------------------------------------------------------------------------------
/src/views/sockets/sockets.service.js:
--------------------------------------------------------------------------------
1 | import {action, runInAction} from 'mobx'
2 |
3 | const SOCKETS_LIST = `${process.env.SYNCANO_REGISTRY_URL}/registry/list/`;
4 | const SOCKETS_GET = `${process.env.SYNCANO_REGISTRY_URL}/registry/get/`;
5 |
6 | export default class {
7 | @action.bound async fetch () {
8 | const {pending} = this.stores
9 |
10 | pending.set('sockets.fetch')
11 |
12 | const res = await this.services.request.get(SOCKETS_LIST)
13 |
14 | runInAction(() => {
15 | pending.delete('sockets.fetch')
16 | this.store.items = res.data
17 | })
18 | }
19 |
20 | @action.bound async fetchSocket ({name, version}) {
21 | const {pending} = this.stores
22 |
23 | pending.set('sockets.fetchSocket')
24 |
25 | const res = await this.services.request.get(SOCKETS_GET, {
26 | params: {
27 | name, version
28 | }
29 | })
30 |
31 | runInAction(() => {
32 | pending.delete('sockets.fetchSocket')
33 | this.store.details = res.data
34 | })
35 | }
36 |
37 | @action.bound async toggleSortDirection () {
38 | this.store.sortDirection = this.store.sortDirection === 'desc' ? 'asc' : 'desc'
39 | }
40 |
41 | @action.bound async sortBy (name) {
42 | this.store.sortBy = name
43 | }
44 |
45 | @action.bound async search (value) {
46 | this.store.search = value
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/views/sockets/sockets.store.js:
--------------------------------------------------------------------------------
1 | import {computed, observable} from 'mobx'
2 | import get from 'lodash.get'
3 |
4 | export default {
5 | @observable details: null,
6 | @observable search: null,
7 | @observable sortBy: 'None',
8 | @observable sortDirection: 'desc',
9 | @observable items: [],
10 | @computed get sortedItems () {
11 | const sorter = composeSorter(this.sortBy, this.sortDirection)
12 | let items = this.items.sort(sorter)
13 |
14 | items = items.map(item => ({
15 | ...item,
16 | select: () => {
17 | this.details = item
18 | }
19 | }))
20 |
21 | if (this.search) {
22 | items = items.filter(item => new RegExp(this.search, 'ig').test(item.name))
23 | }
24 |
25 | return items
26 | }
27 | }
28 |
29 | const composeSorter = (sortBy, direction) => {
30 | const isDesc = direction === 'desc'
31 | const key = {
32 | None: 'created_at',
33 | Name: 'name',
34 | Author: 'author.display_name'
35 | }[sortBy]
36 |
37 | return (a, b) => {
38 | if (get(a, key).toLowerCase() < get(b, key).toLowerCase()) return isDesc ? 1 : -1
39 | if (get(a, key).toLowerCase() > get(b, key).toLowerCase()) return isDesc ? -1 : 1
40 |
41 | return 0
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/views/terms/components/sidebar.js:
--------------------------------------------------------------------------------
1 | import Sidebar from '../../../components/ui/sidebar'
2 | import Menu from '../../../components/ui/menu'
3 | import NavLink from '../../../components/ui/nav-link'
4 |
5 | const TermsSidebar = () => (
6 |
7 |
8 | Terms of service
9 | Acceptable Use Policy
10 | Privacy Policy
11 | General Terms and Conditions
12 | Syncano Performance Insight EU-US Privacy Shield Policy
13 |
14 |
15 | )
16 |
17 | export default TermsSidebar
18 |
--------------------------------------------------------------------------------
/syncano/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | **/.bundles
3 | **/.dist
4 |
--------------------------------------------------------------------------------
/syncano/syncano.yml:
--------------------------------------------------------------------------------
1 | hosting:
2 | staging:
3 | src: ../.zefir
4 | production:
5 | src: ../.zefir
6 |
--------------------------------------------------------------------------------
/zefir.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin')
2 | const {join} = require('path')
3 |
4 | module.exports = {
5 | webpack: (config) => {
6 | config.devtool = false
7 |
8 | // disable soucemaps of babel-loader
9 | for (const r of config.module.rules) {
10 | if (r.loader === 'babel-loader') {
11 | r.options.sourceMaps = 'both'
12 | }
13 | }
14 |
15 | config.module.rules.push(
16 | {
17 | test: /\.svg$/,
18 | use: [
19 | {
20 | loader: 'babel-loader',
21 | options: {
22 | presets: ['env', 'react']
23 | }
24 | }, 'svg-react-loader'
25 | ]
26 | }
27 | )
28 |
29 | for (const plugin of config.plugins) {
30 | if (plugin instanceof HtmlWebpackPlugin) {
31 | plugin.options.template = join(__dirname, 'src', 'index.html')
32 | }
33 | }
34 |
35 | config.output.filename = '[hash].[name]'
36 |
37 | return config
38 | }
39 | }
40 |
--------------------------------------------------------------------------------