Initial project structure with sails.js

This commit is contained in:
2023-11-21 21:57:32 -05:00
commit 523978e520
197 changed files with 76740 additions and 0 deletions

21
views/.eslintrc Normal file
View File

@@ -0,0 +1,21 @@
{
// ╔═╗╔═╗╦ ╦╔╗╔╔╦╗┬─┐┌─┐ ┌─┐┬ ┬┌─┐┬─┐┬─┐┬┌┬┐┌─┐
// ║╣ ╚═╗║ ║║║║ ║ ├┬┘│ │ │└┐┌┘├┤ ├┬┘├┬┘│ ││├┤
// o╚═╝╚═╝╩═╝╩╝╚╝ ╩ ┴└─└─┘ └─┘ └┘ └─┘┴└─┴└─┴─┴┘└─┘
// ┌─ ┌─┐┌─┐┬─┐ ┬┌┐┌┬ ┬┌┐┌┌─┐ ┌─┐┌─┐┬─┐┬┌─┐┌┬┐ ┌┬┐┌─┐┌─┐┌─┐ ─┐
// │ ├┤ │ │├┬┘ │││││ ││││├┤ └─┐│ ├┬┘│├─┘ │ │ ├─┤│ ┬└─┐ │
// └─ └ └─┘┴└─ ┴┘└┘┴─┘┴┘└┘└─┘ └─┘└─┘┴└─┴┴ ┴ ┴ ┴ ┴└─┘└─┘ ─┘
// > An .eslintrc configuration override for use in the `views/` directory.
//
// (This works just like assets/.eslintrc, with one minor addition)
//
// For more information see:
// https://sailsjs.com/anatomy/views/.eslintrc
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
"extends": [
"../assets/.eslintrc"
],
"rules": {
"eol-last": [0]
}
}

10
views/404.ejs Normal file
View File

@@ -0,0 +1,10 @@
<div id="404">
<div style="max-width: 650px;" class="container text-center pt-5 pb-5">
<h1 class="display-1 mb-3">404</h1>
<hr/>
<h2>The page you seek doesn't exist.</h2>
<div>
<p>Think you're seeing this message in error?<br class="d-none d-sm-inline" /><span class="d-inline d-sm-none">&nbsp;</span>Please <a href="/contact">let us know.</a></p>
</div>
</div>
</div>

11
views/498.ejs Normal file
View File

@@ -0,0 +1,11 @@
<div id="498">
<div style="max-width: 650px;" class="container text-center pt-5 pb-5">
<h1>Sorry, that link is expired, or<br class="d-none d-sm-inline" /><span class="d-inline d-sm-none">&nbsp;</span>it has already been used.</h1>
<hr/>
<div>
<p>Need further help accessing your account?<br class="d-none d-sm-inline" /><span class="d-inline d-sm-none">&nbsp;</span>Please <a href="/contact">contact support</a> or request a new <a href="/password/forgot">password reset.</a></p>
<a class="btn btn-lg btn-outline-dark" href="/password/forgot">Reset password</a>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

11
views/500.ejs Normal file
View File

@@ -0,0 +1,11 @@
<div id="500">
<div style="max-width: 650px;" class="container text-center pt-5 pb-5">
<h1 class="display-1 mb-3">Oh my.</h1>
<hr/>
<h2>We've encountered an unexpected error.</h2>
<div>
<p>Need help with your account? Please <a href="/contact">let us know</a>.</p>
<a class="btn btn-lg btn-outline-dark" href="/contact">Open a help request</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,9 @@
<% /* Note: This is injected into `views/layouts/layout-email.ejs` */ %>
<p style="margin-bottom: 25px;">Dear <%= fullName %>,</p>
<p style="margin-bottom: 25px;">Someone requested a password reset for your account. If this was not you, please disregard this email. Otherwise, simply click the button below:</p>
<div style="margin-bottom: 25px; text-align: center;">
<a style="background-color: #00ACC4; display: inline-block; font-size: 1.1em; color: #fff; padding: 10px 35px 10px; font-weight: 500; text-decoration: none; border-radius: 7px;" href="<%= url.resolve(sails.config.custom.baseUrl,'/password/new')+'?token='+encodeURIComponent(token) %>">Update password</a>
</div>
<p style="margin-bottom: 25px;">If you have any trouble, try pasting this link in your browser: <a style="color: #00ACC4; word-wrap: break-word;" href="<%= url.resolve(sails.config.custom.baseUrl,'/password/new')+'?token='+encodeURIComponent(token) %>"><%= url.resolve(sails.config.custom.baseUrl,'/password/new')+'?token='+encodeURIComponent(token) %></a></p>
<p style="margin-bottom: 5px;">Sincerely,</p>
<p style="margin-top: 0px;">The NEW_APP_NAME Team</p>

View File

@@ -0,0 +1,9 @@
<% /* Note: This is injected into `views/layouts/layout-email.ejs` */ %>
<p style="margin-bottom: 25px;">Welcome, <%= fullName %>!</p>
<p style="margin-bottom: 25px;">You're almost ready to get started. Just click the button below to confirm the email address for your account:</p>
<div style="margin-bottom: 25px; text-align: center;">
<a style="background-color: #00ACC4; display: inline-block; font-size: 1.1em; color: #fff; padding: 10px 35px 10px; font-weight: 500; text-decoration: none; border-radius: 7px;" href="<%= url.resolve(sails.config.custom.baseUrl,'/email/confirm')+'?token='+encodeURIComponent(token) %>">Confirm email</a>
</div>
<p style="margin-bottom: 25px;">If you have any trouble, try pasting this link in your browser: <a style="color: #00ACC4; word-wrap: break-word;" href="<%= url.resolve(sails.config.custom.baseUrl,'/email/confirm')+'?token='+encodeURIComponent(token) %>"><%= url.resolve(sails.config.custom.baseUrl,'/email/confirm')+'?token='+encodeURIComponent(token) %></a></p>
<p style="margin-bottom: 5px;">Sincerely,</p>
<p style="margin-top: 0px;">The NEW_APP_NAME Team</p>

View File

@@ -0,0 +1,9 @@
<% /* Note: This is injected into `views/layouts/layout-email.ejs` */ %>
<p style="margin-bottom: 25px;">Dear <%= fullName %>,</p>
<p style="margin-bottom: 25px;">You recently requested an update to the email address for your account. To verify your new email, please click the button below:</p>
<div style="margin-bottom: 25px; text-align: center;">
<a style="background-color: #28AFB0; display: inline-block; font-size: 1.1em; color: #fff; padding: 10px 35px 10px; font-weight: 500; text-decoration: none; border-radius: 7px;" href="<%= url.resolve(sails.config.custom.baseUrl,'/email/confirm')+'?token='+encodeURIComponent(token) %>">Confirm email</a>
</div>
<p style="margin-bottom: 25px;">If you have any trouble, try pasting this link in your browser: <a style="color: #28AFB0; word-wrap: break-word;" href="<%= url.resolve(sails.config.custom.baseUrl,'/email/confirm')+'?token='+encodeURIComponent(token) %>"><%= url.resolve(sails.config.custom.baseUrl,'/email/confirm')+'?token='+encodeURIComponent(token) %></a></p>
<p style="margin-bottom: 5px;">Sincerely,</p>
<p style="margin-top: 0px;">The NEW_APP_NAME Team</p>

View File

@@ -0,0 +1,16 @@
<% /* Note: Unlike other emails, this is NOT intended for use with a layout! */ %>
<div style="background-color: #FAFAFA; width: 100%; font-family: 'Arial', 'Helvetica Neue', 'Helvetica', sans-serif; box-sizing: border-box; padding-bottom: 25px; margin: 0;">
<div style="background: transparent; padding-top: 30px; padding-bottom: 20px; text-align: center;">
<img style="display: inline-block; height: 30px; width: auto; margin-left: auto; margin-right: auto;" alt="Sails" src="https://sailsjs.com/images/logos/sails-logo_ltBg_ltBlue.png"/>
</div>
<div style="background-color: #fff; color: #000; font-size: 1em; border-top: 2px solid #00ACC4; border-bottom: 1px solid #E6E6E6; box-sizing: border-box; padding: 25px; width: 100%; max-width: 600px; margin-left: auto; margin-right: auto;">
<p style="margin-bottom: 25px;">Dear admin,</p>
<p style="margin-bottom: 25px;">We have received the following message through the contact form:</p>
<p><strong>From:</strong> <%= contactName %> &lt;<%= contactEmail %>&gt;</p>
<p><strong>Topic:</strong> <%= topic %></p>
<p style="margin-bottom: 35px;"><strong>Message:</strong> <%= message %></p>
</div>
</div>

View File

@@ -0,0 +1,22 @@
<% /* Default layout for email templates */ %>
<div style="background-color: #FAFAFA; width: 100%; font-family: 'Arial', 'Helvetica Neue', 'Helvetica', sans-serif; box-sizing: border-box; padding-bottom: 25px; margin: 0;">
<div style="background: transparent; padding-top: 30px; padding-bottom: 20px; text-align: center;">
<img style="display: inline-block; height: 30px; width: auto; margin-left: auto; margin-right: auto;" alt="Logo" src="http://via.placeholder.com/200x60/fafafa/000?text=YOUR+LOGO+HERE"/>
</div>
<div style="background-color: #fff; color: #000; font-size: 1em; border-top: 2px solid #00ACC4; border-bottom: 1px solid #E6E6E6; box-sizing: border-box; padding: 25px; width: 100%; max-width: 600px; margin-left: auto; margin-right: auto;">
<%- body %>
</div>
<div style="text-align: center; padding-top: 15px; margin-bottom: 15px; font-size: 12px;">
<a style="color: #00ACC4;" href="<%= url.resolve(sails.config.custom.baseUrl, '/contact') %>">Help</a>
<span style="color: #ccc;">&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a style="color: #00ACC4;" href="<%= url.resolve(sails.config.custom.baseUrl, '/faq') %>">FAQ</a>
<span style="color: #ccc;">&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a style="color: #00ACC4;" href="<%= url.resolve(sails.config.custom.baseUrl, '/legal/terms') %>">Terms of Service</a>
<span style="color: #ccc;">&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a style="color: #00ACC4;" href="<%= url.resolve(sails.config.custom.baseUrl, '/legal/privacy') %>">Privacy Policy</a>
</div>
<p style="color: #777777; padding: 0 25px; max-width: 400px; box-sizing: border-box; margin-left: auto; margin-right: auto; font-size: 10px; text-align: center;">Please do not reply to this email. To get in touch with us, visit our <a style="color: #00ACC4;" href="<%= url.resolve(sails.config.custom.baseUrl, '/contact') %>"> help center.</a> If you have not signed up for an account with us, please disregard this message.</p>
<p style="color: #777777; padding: 0 25px; max-width: 400px; box-sizing: border-box; margin-left: auto; margin-right: auto; font-size: 10px; text-align: center;"><em>(built with <a style="color: #00ACC4;" href="https://sailsjs.com">Sails.js</a>)</em></p>
</div>

201
views/layouts/layout.ejs Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,109 @@
<div id="account-overview" v-cloak>
<account-notification-banner></account-notification-banner>
<div class="container pt-5 pb-5">
<h1>My account</h1>
<hr/>
<div class="row mb-3">
<div class="col-sm-6">
<h4>Personal information</h4>
</div>
<div class="col-sm-6">
<span class="float-sm-right">
<a style="width: 150px" class="btn btn-sm btn-outline-info" href="/account/profile">Edit profile</a>
</span>
</div>
</div>
<div class="row">
<div class="col-3">Name:</div>
<div class="col"><strong>{{me.fullName}}</strong></div>
</div>
<div class="row">
<div class="col-3">Email:</div>
<div class="col">
<strong :class="[me.emailStatus === 'unconfirmed' || me.emailStatus === 'change-requested' ? 'text-muted' : '']">{{me.emailChangeCandidate ? me.emailChangeCandidate : me.emailAddress}}</strong>
<span v-if="me.emailStatus === 'unconfirmed' || me.emailStatus === 'change-requested'" class="badge badge-pill badge-warning">Unverified</span>
</div>
</div>
<hr/>
<div class="row mb-3">
<div class="col-sm-6">
<h4>Password</h4>
</div>
<div class="col-sm-6">
<span class="float-sm-right">
<a style="width: 150px" class="btn btn-sm btn-outline-info" href="/account/password">Change password</a>
</span>
</div>
</div>
<div class="row">
<div class="col-3">Password:</div>
<div class="col"><strong>••••••••••</strong></div>
</div>
<hr/>
<div class="row mb-3" v-if="isBillingEnabled">
<div class="col-sm-6">
<h4>Billing</h4>
</div>
<div class="col-sm-6">
<span class="float-sm-right">
<button style="width: 150px;" class="btn btn-sm btn-outline-info" @click="clickUpdateBillingCardButton()">{{ me.hasBillingCard ? 'Change card' : '+ Add card' }}</button>
</span>
</div>
</div>
<div v-if="isBillingEnabled && me.hasBillingCard">
<div class="row">
<div class="col-3">Credit card:</div>
<div class="col">{{me.billingCardBrand}} ending in <strong>{{me.billingCardLast4}}</strong> <a style="text-decoration: underline; cursor: pointer;" class="ml-2" purpose="remove-button" @click="clickRemoveCardButton()">Remove</a></div>
</div>
<div class="row">
<div class="col-3">Expiration:</div>
<div class="col">{{me.billingCardExpMonth}}/{{me.billingCardExpYear}}</div>
</div>
</div>
<div class="alert alert-danger" v-else-if="isBillingEnabled && cloudError">
There was an error updating your credit card information. Please check your information and try again, or <a href="/contact">contact support</a> if the error persists.
</div>
<div class="alert alert-secondary" v-else-if="isBillingEnabled">
You have not linked a payment source to your account. In order to access paid features, you'll need to provide your credit card information. (Don't worry: you will only be charged when you've reached the limit of your free plan.)
</div>
</div>
<% /* Modify Card Info Modal */ %>
<modal v-if="modal === 'update-billing-card'" @close="closeModal()" v-cloak>
<div class="modal-header">
<h2 class="modal-title">{{me.hasBillingCard? 'Edit' : 'Add' }} card</h2>
</div>
<ajax-form :handle-submitting="handleSubmittingUpdateBillingCard" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedUpdateBillingCard()">
<div class="modal-body">
<div class="form-group">
<label>Credit card</label>
<stripe-card-element :stripe-publishable-key="stripePublishableKey" :is-errored.sync="formErrors.newPaymentSource" v-model="formData.newPaymentSource" key="billing-card" ref="billingcardref"></stripe-card-element>
</div>
<p class="text-danger" v-if="cloudError"><small>An error occured while processing your request. Please check your information and try again, or <a href="/contact">contact support</a> if the error persists.</small></p>
</div>
<div class="modal-footer flex-row-reverse justify-content-start">
<ajax-button :syncing="syncing" class="btn btn-info">Save card</ajax-button>
<button type="button" class="btn btn-outline-danger mr-2" data-dismiss="modal">Cancel</button>
</div>
</ajax-form>
</modal>
<% /* Confirm Remove Card Info Modal */ %>
<modal v-if="modal === 'remove-billing-card'" @close="closeModal()" v-cloak>
<div class="modal-header">
<h5 class="modal-title">Remove Card Info?</h5>
</div>
<ajax-form action="updateBillingCard" :syncing.sync="syncingRemoveCard" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedRemoveCardForm()">
<div class="modal-body text-center">
<p>Are you sure you want to remove your {{me.billingCardBrand}} ending in <strong>{{me.billingCardLast4}}</strong>?</p>
<p class="text-muted mb-0">This may restrict your access to paid features.</p>
<cloud-error v-if="cloudError"></cloud-error>
</div>
<div class="modal-footer border-0 justify-content-center">
<button data-dismiss="modal" class="btn btn-outline-secondary mr-2">Never mind</button>
<ajax-button type="submit" :syncing="syncingRemoveCard" class="btn btn-danger ml-2">Remove</ajax-button>
</div>
</ajax-form>
</modal>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,37 @@
<div id="edit-password" v-cloak>
<account-notification-banner></account-notification-banner>
<div class="container pt-5 pb-5">
<h1>Change password</h1>
<hr/>
<ajax-form action="updatePassword" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedForm()">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="password">New password</label>
<input class="form-control" id="password" name="password" type="password" :class="[formErrors.password ? 'is-invalid' : '']" v-model.trim="formData.password" placeholder="••••••••" autocomplete="new-password" focus-first>
<div class="invalid-feedback" v-if="formErrors.password">Please enter a password or choose "Cancel".</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="confirm-password">Confirm password</label>
<input class="form-control" id="confirm-password" name="confirm-password" type="password" :class="[formErrors.confirmPassword ? 'is-invalid' : '']" v-model.trim="formData.confirmPassword" autocomplete="new-password" placeholder="••••••••">
<div class="invalid-feedback" v-if="formErrors.confirmPassword">Your new password and confirmation do not match.</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<cloud-error v-if="cloudError"></cloud-error>
</div>
<div class="col-sm-6">
<div class="form-group text-right">
<a class="btn btn-outline-info" href="/account">Cancel</a>
<ajax-button type="submit" :syncing="syncing" class="btn btn-dark">Save changes</ajax-button>
</div>
</div>
</div>
</ajax-form>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,38 @@
<div id="edit-profile" v-cloak>
<account-notification-banner></account-notification-banner>
<div class="container pt-5 pb-5">
<h1>Update personal info</h1>
<hr/>
<ajax-form action="updateProfile" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedForm()">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="full-name">Full name</label>
<input class="form-control" id="full-name" name="full-name" type="text" :class="[formErrors.fullName ? 'is-invalid' : '']" v-model.trim="formData.fullName" placeholder="Sturgis P. Sturgeon" autocomplete="name" focus-first>
<div class="invalid-feedback" v-if="formErrors.fullName">Please enter your full name.</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="email-address">Email address</label>
<input class="form-control" id="email-address" name="email-address" type="email" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" placeholder="sturgeon@example.com" autocomplete="email">
<div class="invalid-feedback" v-if="formErrors.emailAddress">Please enter a valid email address.</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<cloud-error v-if="cloudError === 'emailAlreadyInUse'">There is already an account using that email address.</cloud-error>
<cloud-error v-if="cloudError"></cloud-error>
</div>
<div class="col-sm-6">
<div class="form-group text-right">
<a class="btn btn-outline-info" href="/account">Cancel</a>
<ajax-button type="submit" :syncing="syncing" class="btn btn-dark">Save changes</ajax-button>
</div>
</div>
</div>
</ajax-form>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

41
views/pages/contact.ejs Normal file
View File

@@ -0,0 +1,41 @@
<div id="contact" v-cloak>
<div class="container-fluid pt-5 pb-5">
<h1 class="text-center">Get in touch</h1>
<div style="max-width: 650px;" class="mx-auto" v-if="!cloudSuccess">
<p class="text-center">Have a question for us? Maybe some feedback? We love talking to users about NEW_APP_NAME, and we're happy to answer questions about our pricing, roadmap, or business solutions. Send us a note and we'll get back to you as soon as possible.</p>
<hr/>
<ajax-form action="deliverContactFormMessage" :form-errors.sync="formErrors" :form-data="formData" :form-rules="formRules" :syncing.sync="syncing" :cloud-error.sync="cloudError" @submitted="submittedForm()">
<div class="form-group">
<label for="full-name">Name</label>
<input class="form-control" id="full-name" name="full-name" type="text" :class="[formErrors.fullName ? 'is-invalid' : '']" v-model.trim="formData.fullName" placeholder="Sturgis P. Sturgeon" autocomplete="name" focus-first>
<div class="invalid-feedback" v-if="formErrors.fullName">Please let us know what to call you.</div>
</div>
<div class="form-group">
<label for="email-address">Email address</label>
<input class="form-control" id="email-address" name="email-address" type="email" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" placeholder="sturgeon@example.com" autocomplete="email">
<div class="invalid-feedback" v-if="formErrors.emailAddress">Please enter a valid email address.</div>
</div>
<div class="form-group">
<label for="topic">Topic</label>
<input class="form-control" id="topic" name="topic" type="text" :class="[formErrors.topic ? 'is-invalid' : '']" v-model.trim="formData.topic" placeholder="Pricing question" autocomplete="none">
<div class="invalid-feedback" v-if="formErrors.topic">Please choose a topic for your message.</div>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea class="form-control" id="message" name="message" :class="[formErrors.message ? 'is-invalid' : '']" v-model.trim="formData.message" placeholder="What is the difference between the &quot;Individual&quot; plan and the &quot;Professional&quot; plan?" autocomplete="none"></textarea>
<div class="invalid-feedback" v-if="formErrors.message">Message cannot be empty.</div>
</div>
<cloud-error v-if="cloudError"></cloud-error>
<div class="form-group">
<ajax-button type="submit" :syncing="syncing" class="btn btn-dark btn-lg btn-block">Send message</ajax-button>
</div>
</ajax-form>
</div>
<div style="max-width: 450px;" class="mx-auto text-center" v-else>
<p class="text-center">Thanks for reaching out!</p>
<hr/>
<p>We have received your message, and someone from our team will get back to you soon.</p>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,102 @@
<div id="welcome">
<account-notification-banner></account-notification-banner>
<div class="container pt-5 pb-5">
<h1>Welcome!</h1>
<hr/>
<p>This is a page that only logged-in people can visit. Don't you feel special? Try clicking on a button below to do some things you can't do when you're logged out.</p>
<div class="buttons">
<a class="btn btn-info" href="/account/profile">Update my email</a>
<button class="btn btn-outline-info ml-2" @click="clickOpenExampleModalButton()">Open a modal</button>
</div>
</div>
<router-view></router-view>
<!-- ╔═╗═╗ ╦╔═╗╔╦╗╔═╗╦ ╔═╗ ┌┬┐┌─┐┌┬┐┌─┐┬
║╣ ╔╩╦╝╠═╣║║║╠═╝║ ║╣ ││││ │ ││├─┤│
╚═╝╩ ╚═╩ ╩╩ ╩╩ ╩═╝╚═╝ ┴ ┴└─┘─┴┘┴ ┴┴─┘ -->
<modal class="example-modal" v-if="modal==='example'" @close="closeExampleModal()" v-cloak>
<div class="modal-header">
<h2 class="modal-title">Example modal</h2>
<p class="modal-intro">At your leisure, please peruse this excerpt from a whale of a tale.</p>
<hr>
</div>
<div class="modal-body">
<h5 class="pb-2 mb-3 border-bottom">Chapter 1: Loomings.</h5>
<p>Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking people's hats off—then, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me.</p>
<div class="js-timestamp-examples">
<h5 class="pt-4 pb-2 mb-3 border-bottom">About 2 years ago</h5>
<p>
<js-timestamp :at="pageLoadedAt-(2*365*24*60*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(2*365*24*60*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(2*365*24*60*60*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">About 2 months ago</h5>
<p>
<js-timestamp :at="pageLoadedAt-(2*30*24*60*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(2*30*24*60*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(2*30*24*60*60*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">2 days ago</h5>
<p>
<js-timestamp :at="pageLoadedAt-(2*24*60*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(2*24*60*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(2*24*60*60*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">2 hours ago</h5>
<p>
<js-timestamp :at="pageLoadedAt-(2*60*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(2*60*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(2*60*60*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">15 minutes ago</h5>
<p>
<js-timestamp :at="pageLoadedAt-(15*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(15*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(15*60*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">45 seconds ago</h5>
<p>
<js-timestamp :at="pageLoadedAt-(45*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(45*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt-(45*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">Right now</h5>
<p>
<js-timestamp :at="pageLoadedAt"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">In an hour</h5>
<p>
<js-timestamp :at="pageLoadedAt+(60*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt+(60*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt+(60*60*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">In two days</h5>
<p>
<js-timestamp :at="pageLoadedAt+(2*24*60*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt+(2*24*60*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt+(2*24*60*60*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">In about two months</h5>
<p>
<js-timestamp :at="pageLoadedAt+(2*30*24*60*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt+(2*30*24*60*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt+(2*30*24*60*60*1000)" format="calendar"></js-timestamp><br/>
</p>
<h5 class="pt-4 pb-2 mb-3 border-bottom">In about two years</h5>
<p>
<js-timestamp :at="pageLoadedAt+(2*365*24*60*60*1000)"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt+(2*365*24*60*60*1000)" format="timeago" short="true"></js-timestamp><br/>
<js-timestamp :at="pageLoadedAt+(2*365*24*60*60*1000)" format="calendar"></js-timestamp><br/>
</p>
</div>
</div>
<div class="modal-footer flex-row-reverse justify-content-between">
<button type="button" data-dismiss="modal" class="btn btn-info">Close</button>
<button type="button" data-dismiss="modal" class="btn btn-outline-info">Cancel</button>
</div>
</modal>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,11 @@
<div id="confirmed-email" v-cloak>
<div class="container-fluid pt-5 pb-5">
<h1 class="text-center">Email confirmed</h1>
<div style="max-width: 450px;" class="mx-auto text-center">
<hr/>
<p>Congratulations, your account is now verified! You now have full access to your account.</p>
<p><a class="btn btn-outline-info" href="/">To my dashboard</a></p>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,27 @@
<div id="forgot-password" v-cloak>
<div class="container-fluid pt-5 pb-5">
<h1 class="text-center">Recover password</h1>
<div style="max-width: 450px;" class="mx-auto" v-if="!cloudSuccess">
<p class="text-center">Enter your email address below to reset the password for your account.</p>
<hr/>
<ajax-form action="sendPasswordRecoveryEmail" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedForm()">
<div class="form-group">
<input type="email" name="email-address" class="form-control" placeholder="sturgeon@example.com" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" autocomplete="email" focus-first>
<div class="invalid-feedback" v-if="formErrors.emailAddress">Please enter a valid email address.</div>
</div>
<cloud-error v-if="cloudError"></cloud-error>
<div class="form-group">
<ajax-button type="submit" :syncing="syncing" class="btn btn-dark btn-lg btn-block">Send reset link</ajax-button>
</div>
</ajax-form>
<p class="text-center"><a href="/login">Back to login</a></p>
</div>
<div style="max-width: 450px;" class="mx-auto text-center" v-else>
<p>We've sent you a link to update your password.</p>
<hr/>
<p>If the email doesnt arrive after a few minutes, try checking your spam folder. If you still cant find it, please try again, or <a href="/contact">contact support</a>.</p>
<p class="text-center"><a class="btn btn-outline-primary" href="/login">Back to login</a></p>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,29 @@
<div id="login" v-cloak>
<div class="container-fluid pt-5 pb-5">
<h1 class="text-center">Sign in to your account</h1>
<div style="max-width: 450px;" class="mx-auto">
<hr/>
<ajax-form action="login" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedForm()">
<div class="form-group">
<input type="email" class="form-control" placeholder="Email address" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" autocomplete="email" focus-first>
<div class="invalid-feedback" v-if="formErrors.emailAddress">Please provide a valid email address.</div>
</div>
<div class="form-group">
<input type="password" class="form-control" placeholder="Password" :class="[formErrors.password ? 'is-invalid' : '']" v-model.trim="formData.password" autocomplete="current-password">
<div class="invalid-feedback" v-if="formErrors.password">Please enter your password.</div>
</div>
<div class="form-group form-check">
<input class="form-check-input" type="checkbox" id="remember" name="rememberMe" v-model="formData.rememberMe"/>
<label class="form-check-label" for="remember">Remember me</label>
</div>
<cloud-error v-if="cloudError==='badCombo'">The credentials you entered are not associated with an account. Please check your email and/or password and try again.</cloud-error>
<cloud-error v-else-if="cloudError"></cloud-error>
<div class="form-group">
<ajax-button :syncing="syncing" class="btn-dark btn-lg btn-block">Sign in</ajax-button>
</div>
</ajax-form>
<p class="text-center"><a href="/password/forgot">Forgot your password?</a></p>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,26 @@
<div id="new-password" v-cloak>
<div class="container-fluid pt-5 pb-5">
<h1 class="text-center">Reset password</h1>
<div style="max-width: 450px" class="mx-auto">
<hr/>
<ajax-form action="updatePasswordAndLogin" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedForm()">
<div class="form-group">
<label for="password">New password</label>
<input class="form-control" id="password" name="password" type="password" :class="[formErrors.password ? 'is-invalid' : '']" v-model.trim="formData.password" placeholder="••••••••" autocomplete="new-password" focus-first>
<div class="invalid-feedback" v-if="formErrors.password">Please enter a password.</div>
</div>
<div class="form-group">
<label for="confirm-password">Confirm password</label>
<input class="form-control" id="confirm-password" name="confirm-password" type="password" :class="[formErrors.confirmPassword ? 'is-invalid' : '']" v-model.trim="formData.confirmPassword" placeholder="••••••••" autocomplete="new-password">
<div class="invalid-feedback" v-if="formErrors.confirmPassword">Your new password and confirmation do not match.</div>
</div>
<cloud-error v-if="cloudError"></cloud-error>
<div class="form-group">
<ajax-button type="submit" :syncing="syncing" class="btn btn-dark btn-lg btn-block">Submit</ajax-button>
</div>
</ajax-form>
<p class="text-center">Remember your password? <a href="/login">Login</a></p>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,51 @@
<div id="signup" v-cloak>
<div class="container-fluid pt-5 pb-5" v-if="!cloudSuccess">
<h1 class="text-center">Create an account</h1>
<div style="max-width: 450px;" class="mx-auto" >
<p class="text-center">Let's get started! Becoming a member is free and only takes a few minutes.</p>
<hr/>
<ajax-form action="signup" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-errors.sync="formErrors" :form-data="formData" :form-rules="formRules" @submitted="submittedForm()">
<div class="form-group">
<label for="full-name">Full name</label>
<input class="form-control" id="full-name" type="text" :class="[formErrors.fullName ? 'is-invalid' : '']" v-model.trim="formData.fullName" placeholder="Sturgis P. Sturgeon" autocomplete="name" focus-first>
<div class="invalid-feedback" v-if="formErrors.fullName">Please enter your full name.</div>
</div>
<div class="form-group">
<label for="email-address">Email address</label>
<input class="form-control" id="email-address" type="email" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" placeholder="sturgeon@example.com" autocomplete="email">
<div class="invalid-feedback" v-if="formErrors.emailAddress">Please enter a valid email address.</div>
</div>
<div class="form-group">
<label for="password">Choose a password</label>
<input class="form-control" id="password" type="password" :class="[formErrors.password ? 'is-invalid' : '']" v-model.trim="formData.password" placeholder="••••••••" autocomplete="new-password">
<div class="invalid-feedback" v-if="formErrors.password">Please enter a password.</div>
</div>
<div class="form-group">
<label for="confirm-password">Confirm password</label>
<input class="form-control" id="confirm-password" type="password" :class="[formErrors.confirmPassword ? 'is-invalid' : '']" v-model.trim="formData.confirmPassword" placeholder="••••••••" autocomplete="new-password">
<div class="invalid-feedback" v-if="formErrors.confirmPassword">Your password and confirmation do not match.</div>
</div>
<div class="form-group form-check">
<input class="form-check-input" id="terms-agreement" type="checkbox" v-model="formData.agreed">
<label for="terms-agreement" class="form-check-label" :class="[formErrors.agreed ? 'text-danger' : '']">I have read &amp; agree to the <a target="_blank" href="/terms">terms of service</a>.</label>
</div>
<cloud-error v-if="cloudError==='emailAlreadyInUse'">It looks like there's already an account with your email address. If you forgot your password, you can recover it <a href="password/forgot">here</a>.</cloud-error>
<cloud-error v-else-if="cloudError"></cloud-error>
<div class="form-group">
<ajax-button type="submit" :syncing="syncing" class="btn-dark btn-lg btn-block">Create account</ajax-button>
</div>
</ajax-form>
<p class="text-center">Have an account? <a href="/login">Sign in</a></p>
</div>
</div>
<div class="container-fluid pt-5 pb-5" v-if="cloudSuccess">
<h1 class="text-center">Check your email!</h1>
<div class="text-center">
<hr/>
<p>Your account is nearly ready. All you have to do is click the link we sent to <strong>{{formData.emailAddress}}</strong>.</p>
<p>(You can still access your dashboard now, but some features will be unvavailable until we've verified your email address.)</p>
<p><a class="btn btn-outline-info" href="/">Go to dashboard</a></p>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

151
views/pages/faq.ejs Normal file
View File

@@ -0,0 +1,151 @@
<div id="faq">
<div class="container pt-5 pb-5">
<h1>FAQ</h1>
<div>
<hr class="mt-4 mb-4"/>
<h4>What is this page for?</h4>
<p>This is a template for building your own "Frequently Asked Questions" page. It was generated automatically as part of the expanded seed app provided by <a href="https://sailsjs.com/about">Sails</a>. On top of its primary purpose as a template, this page doubles as a handy guide about the seed app itself.</p>
<p><small>Before you deploy this app to production, be sure to replace the content on this page with real questions and answers that are relevant to your project. Or if you don't need an FAQ, just delete this file altogether.</small></p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>Other than Sails, what technologies, frameworks, or services does this app rely on?</h4>
<p>This app includes several tools and integrations that we use frequently on top of Sails. They've worked well for us in the past; allowing us to focus on developing new features with minimal overhead. Some are files we've rolled ourselves, and the rest are 3rd party services and frameworks. While a couple of these will require you to make an account (or obtain an API key, etc.), we want to emphasize that we only included trusted, reliable tools that we feel comfortable using on our own projects, and our customers' projects.</p>
<ul class="pl-3 pl-sm-5">
<li><a target="_blank" href="http://getbootstrap.com/docs/4.0/getting-started/introduction/"><strong>Bootstrap 4</strong></a> - Front-end component library</li>
<li><a target="_blank" href="http://fontawesome.io/icons/"><strong>Font Awesome 4</strong></a> - Icons</li>
<li><a target="_blank" href="https://vuejs.org/"><strong>Vue.js</strong></a> - Front-end framework</li>
<li><a target="_blank" href="https://npmjs.com/package/parasails"><strong>parasails.js</strong></a> - Thin layer of bundled conventions for using Vue.js with Sails.js, and dynamically-generated SDK for handling AJAX and/or WebSocket requests from the front-end to the actions in your Sails app. (More info on usage below.)</li>
<li><a target="_blank" href="https://www.sendgrid.com/"><strong>Sendgrid</strong></a>* - Emails</li>
<li><a target="_blank" href="https://stripe.com/docs"><strong>Stripe</strong></a>* - Payments</li>
</ul>
<p><small>* requires an API key</small></p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>Why do I see placeholders like "NEW_APP_NAME" all over the place? What's the best way to customize these?</h4>
<p>Because projects are often generated before product and/or company names are finalized, we felt it made more sense to use easily find-and-replaceable placeholders than to prompt for an app or company name at the beginning. (It's a lot easier to find/replace "NEW_APP_NAME" without conflicts than it is to find/replace a more commonly-used word.)</p>
<p>When you've got your product, company name, and branding finalized, here are the placeholders you'll want to switch out:</p>
<ul class="pl-3 pl-sm-5">
<li><strong purpose="placeholder">NEW_APP_NAME</strong> - You can replace this with the name of your product.</li>
<li><strong purpose="placeholder">NEW_APP_COMPANY_NAME</strong> - This placeholder is used for the entity name in the default legal terms &amp; privacy policy, and can be replaced with the name of the company behind your app.</li>
<li><strong purpose="placeholder">DATE_PRIVACY_POLICY_LAST_UPDATED</strong> - Replace this with the date your Privacy Policy was updated (so if you're switching out the company name: today).</li>
<li><strong purpose="placeholder">DATE_TERMS_OF_SERVICE_LAST_UPDATED</strong> - Replace this with the date your Terms of Service were updated (so if you're switching out the company name: today).</li>
<li><strong purpose="placeholder">NEW_APP_COMPANY_ABOUT_HREF</strong> - This is the link in the copyright section of the footer. You'll probably want to switch it out for a link to your company's about page or marketing site.</li>
<li>Placeholder logo - To use your custom branding, you can simply replace the image file at <code>assets/images/logo.png</code> with a custom image of the same name, or change the image <code>src</code> in the header in <code>views/layouts/layout.ejs</code>.</li>
</ul>
<blockquote><small>Just a heads up, the previous couple paragraphs will make a lot less sense once you've done a find/replace on each of those placeholder words.</small></blockquote>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>How do I configure a new method to use with cloud.js?</h4>
<p>Once you've created a controller action and added it to your routes (see the <a target="_blank" href="https://sailsjs.com/documentation/concepts/routes">Sails.js docs for more info on how to do that</a>), there are just a few simple steps to add it to your global <code>Cloud</code> SDK:</p>
<ul class="pl-3 pl-sm-5">
<li>Rebuild this app's custom SDK using <code>sails run scripts/rebuild-cloud-sdk</code>. (This automatically regenerates the method definitions in your <code>assets/js/cloud.setup.js</code> file based on your configured routes.)</li>
<li>You can then call any relevant new actions as "cloud methods" in your frontend code like so: <code>await Cloud.doSomething.with({…})</code></li>
</ul>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>How do I register a new page with parasails.js?</h4>
<p>To generate files for a new page (for example, "hotels-overview"), run:</p>
<p><code>sails generate page hotels-overview</code></p>
<p>Then you can add a route for the new view action to make it accessible from the browser. For example:</p>
<p><code>'GET /hotels': { action: 'view-hotels-overview' }</code></p>
<p><small>To register a new page without the generator, make sure the top-level element of the view you wish to register has an <code>id</code> property (e.g. <code>&lt;div id="my-new-page"&gt;...&lt;/div&gt;</code>). Then, create a new javascript file, and include <code>parasails.registerPage('my-new-page', { /* options for the Vue.js instance */ })</code>. For more thorough examples, dig around in <code>assets/js/pages/</code>.</small></p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>What's up with all the inline styles?</h4>
<p>Since this starter app uses Bootstrap, we wanted to make it a little clearer where our custom styles end and Bootstrap styles begin. Wherever possible, you'll see <code>style="..."</code> in the HTML. If for some reason a style tag wouldn't work (e.g. for media queries), you'll see <code>purpose="..."</code> in the HTML, and <code>[purpose='...']</code> in the .less file we took that approach, rather than using custom class names, to help avoid confusion about which classes come from Bootstrap and which ones we added ourselves.</p>
<p>Another reason: copy/pasting! With the styles inline, it's easy to copy HTML from a page without the need to also duplicate styles from the matching .less files.</p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>How do I deploy to Heroku?</h4>
<p>In your <a target="_blank" href="https://dashboard.heroku.com">Heroku dashboard</a>, configure your app to auto-deploy from the <code>deploy</code> branch of this project's GitHub repository. Then, from the command line, run <code>sails run deploy</code>.</p>
<p>For more deployment tips, <a href="https://sailsjs.com/documentation/concepts/deployment">click here</a>.</p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>Do I have to host on Heroku?</h4>
<p>No way! You can deploy your app <a target="_blank" href="https://sailsjs.com/documentation/concepts/deployment/hosting">anywhere that supports Node.js</a>.</p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>How do I verify my user's email addresses?</h4>
<p>By default, email verification is neither required for signup, nor for when an existing user changes their email address from their account page. To enable an email verification step for both of these actions, open <code>config/custom.js</code> and change <code>verifyEmailAddresses: false</code> to <code>verifyEmailAddresses: true</code>.</p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>How do I finish setting up Sendgrid?</h4>
<p>To use Sendgrid, you'll need to <a target="_blank" href="https://www.sendgrid.com/">sign up for an account</a>. (While this <em>is</em> a paid service at higher usage levels, you should be able to use Sendgrid in development without having to provide any credit card information.)</p>
<p>After you've created an account, you'll need to add your Sendgrid API secret to your custom config. (In development, this will be either in <code>config/custom.js</code>, or in a <code>local.js</code> file you add to your <code>config/</code> folder. For your staging or production deployment, you'll want to set these using system environment variables -- aka "config variables" in Heroku.)</p>
<p>If you already own a domain for your app, you can follow Sendgrid's instructions for configuring a sending domain.</p>
<p>Otherwise, to send emails from this app <em>without</em> configuring a sending domain, you can <a target="_blank" href="https://app.sendgrid.com/settings/sender_auth">verify a single sender</a>. Just be sure to set <code>sails.config.custom.fromEmailAddress</code> to use the verified email, or there will be errors from the endpoints that attempt to send emails.</p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>How do I finish setting up Stripe?</h4>
<p>To use Stripe for your app's payment processing, you'll need to <a target="_blank" href="https://stripe.com">sign up for an account</a>.</p>
<p>Once you have an account, you'll need to include your publishable and secret keys in your app's custom config as <code>sails.config.custom.stripePublishableKey</code> and <code>sails.config.custom.stripeSecret</code>. In development, you can add your test keys to <code>config/custom.js</code>, or in a <code>local.js</code> file you add to your <code>config/</code> folder.</p>
<p>In your staging or production deployment, you'll want to set your API keys using system environment variables (aka "config variables" in Heroku), and you will need ensure that your site meets Stripe's HTTPS requirements in order for Stripe Checkout to work. For more information, see Stripe's <a target="_blank" href="https://stripe.com/docs/checkout">Detailed Checkout Guide</a>.</p>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>How do I disable ___________?</h4>
<p class="pt-4 text-muted"><strong><em>To disable the Bootstrap framework:</em></strong></p>
<ul class="pl-3 pl-sm-5">
<li>Delete the folder <code>assets/dependencies/bootstrap/</code></li>
<li>In <code>tasks/pipeline.js</code>, delete <code>'dependencies/bootstrap/dependencies/**/*.js',</code> from <code>jsFilesToInject</code></li>
</ul>
<p class="pt-4 text-muted"><strong><em>To disable FontAwesome:</em></strong></p>
<ul class="pl-3 pl-sm-5">
<li>Delete the folder at <code>assets/dependencies/font-awesome-4/</code>. (You'll also likely want to search the project for "fa fa-" to check for any lingering icons in the HTML.)</li>
</ul>
<p class="pt-4 text-muted"><strong><em>To swap out Sendgrid:</em></strong></p>
<p>We chose Sendgrid because of its very generous free tier (you can send up to 100 emails per day). Plus, it integrates easily, it's been around for a while (so it's stable and does a good job evading spam filters), and it's used internally at companies like Slack and Medium.</p>
<p>Out-of-the box, if Sendgrid is not configured for this app, the contact form and password recovery flow will fail outright unless an email address ending in "@example.com" is used. Luckily, if you wish to use <a target="_blank" href="https://stackshare.io/twilio-sendgrid">a different solution for sending automated emails</a>, the logic for sending emails is fairly contained. To switch to a service other than Sendgrid, you'll need to make the following changes to your code:</p>
<ul class="pl-3 pl-sm-5">
<li>Modify the code in <code>api/helpers/send-template-email.js</code> to use the email service of your choice.</li>
<li>In <code>api/hooks/custom/index.js</code>, remove the warnings related to Sendgrid.</li>
</ul>
<p class="pt-4 text-muted"><strong><em>To remove or replace Stripe integration:</em></strong></p>
<p>We chose Stripe because it is the easiest-to-integrate payment processor in the industry. Its rates are about the same <a target="_blank" href="https://stackshare.io/stripe/alternatives">as its competitors'</a>, but even if that weren't the case, Stripe's focus on developer happiness would probably make up for it. Stripe processes payments, payouts, and more for <a target="_blank" href="https://stripe.com/us/customers">hundreds of thousands of businesses</a> all around the world.</p>
<p>By default, features related to billing are automatically disabled if you don't have a Stripe publishable key &amp; secret key in your custom config (<code>sails.config.custom.stripePublishableKey</code> and <code>sails.config.custom.stripeSecret</code>, respectively). Your app will not be negatively impacted or appear broken; it will merely have some extraneous code in places. If you don't anticipate integrating billing features into your app and want to remove this code entirely, you can make the following changes:</p>
<ul class="pl-3 pl-sm-5">
<li>In the <code>User</code> model definition at <code>api/models/User.js</code>, remove the <code>stripeCustomerId</code>, <code>billingCardBrand</code>, <code>billingCardLast4</code>, <code>billingCardExpMonth</code>, and <code>billingCardExpYear</code> attribute definitions.</li>
<li>In the "signup" action at <code>api/controllers/entrance/signup.js</code>:
<ul class="pl-3 pl-sm-5">
<li>Remove the line at the top requiring the Stripe dependency</li>
<li>Remove the block of code creating a Stripe customer. (If you have trouble finding where this happens, be sure to read the comments.)</li>
<li>In the call to <code>User.create()</code>, remove the <code>stripeCustomerId</code> property.</li>
</ul>
</li>
<li>In the "confirm email" action at <code>api/controllers/entrance/confirm-email.js</code>:
<ul class="pl-3 pl-sm-5">
<li>Remove the line at the top requiring the Stripe dependency.</li>
<li>Remove the block of code that handles creating/updating a Stripe customer. (If you have trouble finding where this happens, be sure to read the comments.)</li>
</ul>
</li>
<li>Remove the update billing card endpoint and all references to it by doing the following:
<ul class="pl-3 pl-sm-5">
<li>Delete the file at <code>api/controllers/account/update-billing-card.js</code>.</li>
<li>In <code>config/routes.js</code>, delete the route configuration for <code>'PUT /api/v1/account/update-billing-card'</code>.</li>
<li>In <code>assets/js/cloud.setup.js</code>, delete the <code>updateBillingCard</code> method.</li>
<li>In <code>assets/js/pages/account/account-overview.page.js</code>, remove the <code>clickStripeCheckoutButton</code> method.</li>
<li>In <code>views/pages/account/account-overview.ejs</code>, remove the HTML related to billing.</li>
</ul>
</li>
<li>In <code>api/hooks/custom/index.js</code>, remove the warnings related to Stripe.</li>
<li>In <code>api/hooks/custom/index.js</code>, remove <code>sails.config.custom.enableBillingFeatures = !isMissingStripeConfig;</code>.</li>
</ul>
</div>
<div>
<hr class="mt-4 mb-4"/>
<h4>Where can I go for more help?</h4>
<p>If you run into trouble, you can often find the answer in the <a target="_blank" href="http://sailsjs.com/documentation">Sails.js documentation</a>. If you're unsure about how to proceed, or completely stumped, be sure to check out the <a target="_blank" href="http://sailsjs.com/support">latest available resources</a>.</p>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

106
views/pages/homepage.ejs Normal file
View File

@@ -0,0 +1,106 @@
<div id="homepage" v-cloak>
<div style="padding-top: 100px; padding-bottom: 25px; color: #14acc2;" class="d-flex flex-column justify-content-center position-relative" purpose="full-page-hero">
<div class="container text-center">
<div style="width: 220px; height: 170px;" class="mx-auto position-relative">
<img style="width: 170px; left: 25px; top: 25px;" class="position-absolute" alt="The sky above the ocean" src="/images/hero-sky.png"/>
<img style="width: 80px; top: 55px; left: -40px;" class="position-absolute" purpose="cloud-1" alt="A grayish-blue cloud" src="/images/hero-cloud.png"/>
<img style="width: 80px; top: 45px; left: -40px;" class="position-absolute" purpose="cloud-2" alt="Another grayish-blue cloud" src="/images/hero-cloud.png"/>
<img style="width: 160px; bottom: 50px; left: 18px;" class="position-absolute" purpose="ship" alt="A ship hovering a few feet over the surface of the water, bearing a flagpole with the Sails.js logo" src="/images/hero-ship.png"/>
<img style="width: 170px; bottom: 40px; left: 25px;" class="position-absolute" alt="The shadow of the floating ship on the water" src="/images/hero-water.png"/>
</div>
<h1 class="display-4 pb-5">A new <strong>Sails</strong> app.</h1>
<div>
<div style="cursor: pointer; bottom: 25px; left: 0;" class="position-absolute w-100 mt-5" purpose="more-info-text" @click="clickHeroButton()">
<div style="letter-spacing: 2px;" class="text-uppercase font-weight-bold">Dive in</div>
<div style="font-size: 20px;">&darr;</div>
</div>
</div>
</div>
</div>
<div style="background-color: rgba(238, 245, 249, 0.72);" class="text-center py-5" purpose="scroll-destination">
<div class="container pt-3 pb-5" purpose="about-section">
<h3>This is your freshly-generated project + a few extras.</h3>
<p style="max-width: 800px;" class="mx-auto">In our time <a target="_blank" href="https://sailsjs.com/about">building apps for customers</a>, we've found ourselves re-rolling some of the same key features over and over again between projects. So we picked some of the things that kept popping up, implemented our own opinionated solutions for them, and then included it all in this free and open-source starter app. We hope it helps you as much as it helps us!</p>
<div class="row pt-5">
<div class="mb-4 mb-sm-0 col-sm">
<div style="color: #fff; background-color: #14acc2; font-size: 35px; line-height: 75px; height: 75px; width: 75px;" class="rounded-circle mx-auto text-center mb-3">
<i class="fa fa-envelope"></i>
</div>
<h4>Emails</h4>
<p class="text-muted">Built-in support for internal emails from the <a href="/contact">contact form</a>, as well as transactional emails for users.</p>
</div>
<div class="pt-4 pt-sm-0 mb-4 mb-sm-0 col-sm">
<div style="color: #fff; background-color: #14acc2; font-size: 35px; line-height: 75px; height: 75px; width: 75px;" class="rounded-circle mx-auto text-center mb-3">
<i class="fa fa-lock"></i>
</div>
<h4>Authentication</h4>
<p class="text-muted">Ready-to-go, customizable <a href="/signup">sign up</a>, <a href="/login">login</a>, and <a href="/password/forgot">password recovery</a> flows for your users.</p>
</div>
<div class="pt-4 pt-sm-0 col-sm">
<div style="color: #fff; background-color: #14acc2; font-size: 35px; line-height: 75px; height: 75px; width: 75px;" class="rounded-circle mx-auto text-center mb-3">
<i class="fa fa-credit-card"></i>
</div>
<h4>Billing</h4>
<p class="text-muted">Hook up to your Stripe account for managing customers and payment sources.</p>
</div>
</div>
<a class="btn btn-outline-info mt-3" href="/faq">Learn more</a>
</div>
</div>
<div class="container pt-5">
<div class="pt-5" purpose="setup-section">
<h3 class="text-center">How to get started:</h3>
<div class="position-relative my-5" purpose="setup-step">
<div style="left: 0; top: 0; width: 140px;" class="position-absolute d-none d-lg-block">
<img alt="computer with email symbol on screen" src="/images/setup-email.png" class="w-100"/>
</div>
<h5>Set up your branding and content</h5>
<p>This starter app includes several different page templates (including the one you're reading right now) that you can change as much or as little as you like. If there are any pages you won't need, just remove them.</p>
<p>To get started, do a global find/replace in this project for occurrences of <code>NEW_APP_NAME</code> and replace them all with the actual name of your app or platform (e.g. "Facebag"). Then do the same thing again to replace <code>NEW_APP_COMPANY_NAME</code> with the actual name of your organization (e.g. "Facebag Corporation").</p>
<blockquote><small>This app also includes a default <a href="/legal/terms">Terms of Use</a> and <a href="/legal/privacy">Privacy Policy</a>. We want to make it easier for apps to be transparent about their users' rights and privacy. But we are developers, not lawyers; and this is <em>definitely not legal advice</em>. Before going live, be sure to replace these example documents with your own company's policies, and <a href="https://en.wikipedia.org/wiki/Counsel" target="_blank">seek counsel</a> for assistance if you need to design new terms from scratch.</small></blockquote>
</div>
<div class="position-relative my-5" purpose="setup-step">
<div style="left: 0; top: 0; width: 140px;" class="position-absolute d-none d-lg-block">
<img alt="computer with credit card icon on screen" src="/images/setup-payment.png" class="w-100"/>
</div>
<h5>Configure integrations</h5>
<p>In order for this app to send automated emails, you'll need to create a <a target="_blank" href="https://sendgrid.com/">Sendgrid</a> account. Then, in <code>config/custom.js</code>, configure the following:</p>
<div class="card bg-light mb-4">
<div class="card-body"><pre><code><span class="text-muted">// Recipient of contact form messages</span>
internalEmailAddress: my.email@example.com,
<span class="text-muted">// For outgoing emails</span>
fromEmailAddress: 'noreply@example.com',
fromName: 'The NEW_APP_NAME Team',
<span class="text-muted">// Sendgrid settings</span>
sendgridSecret: 'SG.fake.3e0Bn0qSQVnwb1E4qNPz9JZP5vLZYqjh7sn8S93oSHU'</code></pre>
</div>
</div>
<p>To enable support for billing features, you'll need to make a <a target="_blank" href="https://stripe.com">Stripe</a> account, then include your test credentials like so:</p>
<div class="card bg-light mb-4">
<div class="card-body"><pre><code><span class="text-muted">// Stripe credentials</span>
stripePublishableKey: 'pk_test_Zzd814nldl91104qor5911gjald',
stripeSecret: 'sk_test_Zzd814nldl91104qor5911gjald'</code></pre>
</div>
</div>
<blockquote><small>If your app doesn't need payment processing, it will still work without Stripe configuration. In this case, all references to billing will just be omitted from the UI.</small></blockquote>
</div>
<div class="position-relative my-5" purpose="setup-step">
<div style="left: 0; top: 0; width: 140px;" class="position-absolute d-none d-lg-block">
<img alt="computer with sails logo on screen" src="/images/setup-customize.png" class="w-100"/>
</div>
<h5>Customize</h5>
<p>Once the initial configuration is done, you're ready to start building out the rest of your app.</p>
<p>We worked hard to make this starter app's structure consistent and its files are as bare-bones as possible, so it's easy to add new pages and business logic following the conventions we set up. If you run into trouble, have a look at <a href="/faq">your new FAQ page</a>, which covers the tools we used and how to customize. For a deeper dive, check out the Sails <a href="https://courses.platzi.com/courses/sails-js/">walkthrough</a>, <a href="https://sailsjs.com/documentation">reference documentation</a> and <a href="https://sailsjs.com/support">support</a> pages.</p>
</div>
</div>
<hr/>
<div class="text-center py-5" purpose="pep-talk">
<h3>Let's get to work.</h3>
<p style="max-width: 800px;" class="mx-auto">We think this project is a pretty convenient starting point, but of course there's no one-size-fits-all solution. The good news is, this app is in your hands now, so you can jump into the files and adapt it to your needs. Change some code around. Break stuff. Fix it. And above all: <strong>make something people want to use</strong>.</p>
<p class="mb-5"><span class="text-muted">&hearts;</span> <a href="https://sailsjs.com/about" class="border-0">The Sails Team</a></p>
</div>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,82 @@
<div id="privacy">
<div class="container pt-5 pb-5">
<h1>NEW_APP_COMPANY_NAME privacy policy</h1>
<hr/>
<p>At NEW_APP_COMPANY_NAME we care deeply about your privacy, and we take pride in being a good steward of any data you choose to share with us. If you have any questions about how we use or handle user data, just <a href="/contact">ask</a> and we'll get back to you in a jiffy.</p>
<h3>General information</h3>
<p>We believe it is important for you to understand what types of information we are collecting and what may happen to that information when using our website and related applications and resources (collectively, the “Service”). This Privacy Policy is designed to explain NEW_APP_COMPANY_NAMEs policies and procedures with respect of collection, use and disclosure by NEW_APP_COMPANY_NAME of information about you in connection with the Service. This Privacy Policy has a limited scope and applies only to the Service.</p>
<p>By accessing the Service, you consent to the collection, use and disclosure of your personal information as explained in this privacy policy (the “Privacy Policy”). This Privacy Policy is incorporated into and is subject to the Terms of Service, which can be accessed <a href="/terms">here</a>. Your use of the Service and any information you provide through the Service is subject to the terms of this Privacy Policy and our Terms of Service.</p>
<h3>What you will find in this privacy policy?</h3>
<ol>
<li>How we collect information about you</li>
<li>How we use or share information about you</li>
<li>How we safeguard information about you</li>
<li>Other matters related to the information we collect about you</li>
</ol>
<h3>Collection of information</h3>
<p>We collect information about you in a variety of ways in connection the Service. NEW_APP_COMPANY_NAME will be the controller of your personal data processed in connection with the Service, and can be contacted <a href="/contact">here</a>.</p>
<h5>Registration</h5>
<p>To use some features and resources available through the Service, a user must complete our registration process. During registration, a user is required to provide certain categories of information, which may include personally identifiable information and other information about you such as your name, address, e-mail address, social network account information, password, user type, birthdate, gender, phone number, and how you heard about NEW_APP_COMPANY_NAME.</p>
<h5>Optional information</h5>
<p>At various other points during your use of the Service, you may have the option to provide additional information related to you. For example, you may wish to create a profile, post, comment on another users post or share a post with another person, or you may wish to utilize social network integrations or location-based features of the Service. For voluntary activities like these you have the choice about whether to disclose the information necessary to use these features of the Service.</p>
<h5>Passive collection of information</h5>
<p>In addition to the information which you actively provide to us by methods such as completing our registration process, commenting on, or sharing a post, we collect information about how you interact with the Service. We collect passive information for purposes such as testing and improving user experience with the Service and the compilation of demographic data and related usage information for internal purposes and for disclosure to third parties such as advertisers and business partners. The types of passive information we collect include, without limitation, certain standard information sent by your browser, such as your Internet Protocol (IP) address, your device ID, your browser type and capabilities and language, your operating system, the date and time you access the Service, and the referring website from which you linked to the Service. We also collect information related to your behavior within the Service (such as pages viewed, links clicked, and other actions taken within the Service). We also may utilize special links, electronic images or other objects embedded within the Site—sometimes referred to as web beacons or tracking pixels—to collect information that helps determine and analyze how you and other visitors use the Site. If you have provided us with personal information through registration or other voluntary means, we may connect your personal information with passive information collected through our Site and use the combined information to help determine your potential interests in certain products, services or information and for other purposes described in this Privacy Policy.</p>
<h5>Cookies</h5>
<p>Information related to your interactions with the Service may be collected and stored by us in small data files—commonly referred to as cookies—that are placed onto your device by our servers. We may use temporary session cookies, which are terminated when you close your current session, to store certain personal information you provide for purposes of administering that session and facilitating transactions requested by you during that session. We also may use persistent cookies, which remain saved on your device after your current session is closed, to store information related to your interactions with the Service to facilitate future sessions. Most browsers automatically accept cookies, but you can usually modify your browser settings to decline cookies or to notify you when a cookie is being placed on your device. Each browser is different, but generally users can remove cookies by following directions provided in the browsers “help” file. Please note that, if you reject cookies or disable cookies, some features of the Service may not function properly.</p>
<h5>Collection of information from minors</h5>
<p>The Service is intended for a general audience and it is not intended for children under the age of 16. Use of the Service is prohibited for anyone under the age of 16. If you are between 16 and 18 years of age, you must secure authorization from your parents or legal guardian before using the Service. Although the Service may contain information that may be of interest to children, the Service is not directed at children and we do not knowingly collect or solicit personal information from children under the age 16.</p>
<h3>Use and sharing of information</h3>
<p>The Service enables that enable users to post, access and share user generated content and other information across the NEW_APP_COMPANY_NAME community, other social networks and other websites and online services and may include various features and functionality such as comments, message boards or chat areas, where users can post information and communicate with one another. Please be aware that these posts and communications are being made publicly available online and you do so at your own risk.</p>
<p>We will not knowingly use, share, or otherwise transfer information about you other than in accordance with the terms set forth in this Privacy Policy. Additionally, we may use, share, or otherwise transfer information about you for other purposes for which we have obtained your consent. Your consent may be implied, deemed (using an opt-out mechanism) or express. Express consent can be given orally, electronically or in writing. Implied consent is consent that can reasonably be inferred from your action or inaction. Regardless of your decision regarding the sharing of your personal information, we may share demographic data and related usage information with our business partners in an aggregated or anonymized manner intended to remove or obscure any personal information which can identify any individual user.</p>
<p>We may use information about you in order to deliver products and services to you and to customize the Service to your interests and enhance your overall experience with the Service. We try to recognize you using various methods, including having you log in with your user name and your password. Once we recognize you, your experience may be customized for you so that you can see what interests you the most. In addition, as described above, we utilize passive information to learn more about how you interact with the Service. This passive information may be combined with other information and used for purposes such as enhancing your experience using the Service, improving the Service, testing and enhancing security and compilation of demographic data and related usage information for our internal purposes and for other purposes described in this Privacy Policy.</p>
<p>While we strive to protect the personal information of our users, we may use, share, or otherwise transfer your personal without your knowledge or consent if required by applicable law or regulatory requirements or in the good-faith belief that such action is necessary or appropriate.</p>
<p>From time to time, we may decide that it is more efficient to use third parties to perform specific functions related to the Service. If we use a third party to perform specific functions (such as sending communications to our users on our behalf), then information (such as contact information) may be shared with that third party to the extent necessary for that third party to perform that function. We authorize these third party service providers to use personally identifiable information only for the purpose of providing the specific services requested by us. Third party service providers covered by this paragraph do not include third party sites, applications or services that are accessed via links within the Service, which are governed by a more specific provision below.</p>
<p>If you opt-in to a feature that allows third parties to contact you, personal information about you (such as your contact information and other information collected during your visit to the Service) may be shared with data aggregators, marketers, and other organizations (possibly in the form of list rental).</p>
<p>In the event that we sell, assign or transfer some or all of our business to a successor or acquirer, we may sell, assign or transfer all of your information, regardless of your status, to such successor or acquirer.</p>
<h3>How long we store information</h3>
<p>NEW_APP_COMPANY_NAME will store your data as long as needed to provide you with our services and to operate our organization. If you ask NEW_APP_COMPANY_NAME to delete specific personal information, we'll honor your request unless deleting that information prevents us from carrying out necessary functions, like calculating taxes or conducting required audits.</p>
<h3>Security</h3>
<p>We have security measures in place to help protect against the unauthorized access to personally identifiable information under our control. We utilize both online and offline security methods, including firewalls, passwords and restricted physical access to the places where your information is stored to help protect personally identifiable information. Our team is trained to comply with our security procedures, and our security procedures are regularly reviewed and revised as we deem necessary.</p>
<h3>How you can access and update your information</h3>
<p>Our goal is for any personal information in our possession to be as accurate, current and complete as necessary for the purposes for which we use that information. If you want to review, verify or correct your personal information, please contact us using the contact information set out below. We reserve the right not to change any personal information and instead append any alternative information the individual concerned believes is appropriate. To some extent, we rely on individuals to update their own personal information. Unless an individual advises us of these important changes, we may have no way of knowing about them.</p>
<h3>Your legal rights</h3>
<p>The State of California enacted the Shine the Light law (CA Civil Code Sec 198.83) that permits users who are California residents to request certain information regarding the disclosure of personal information during the past year for marketing purposes. To make such a request, you can <a href="/contact">contact us</a>.</p>
<p>If you are in the European Union, you have certain rights under the General Data Protection Regulation in relation to your personal data:</p>
<ul>
<li><strong>Access</strong> - You are welcome to request NEW_APP_COMPANY_NAME for copies of your personal data. We may charge you a small fee for this service.</li>
<li><strong>Rectification</strong> - You may request that NEW_APP_COMPANY_NAME correct any information you believe is inaccurate. You may also request NEW_APP_COMPANY_NAME to complete the information you believe is incomplete.</li>
<li><strong>Erasure</strong> - You may request that NEW_APP_COMPANY_NAME erase your personal data, under certain conditions.</li>
<li><strong>Restriction of processing</strong> - You may request that NEW_APP_COMPANY_NAME restrict the processing of your personal data, under certain conditions.</li>
<li><strong>Objection to processing</strong> - You may object to NEW_APP_COMPANY_NAME's processing of your personal data, under certain conditions.</li>
<li><strong>Data portability</strong> - You may request that NEW_APP_COMPANY_NAME transfer the data that we have collected to another organization, or directly to you, under certain conditions.</li>
</ul>
<p>If you make such a request, please be sure to include your contact information. We will reply within 30 days. If you would like to exercise any of these rights, please <a href="/contact">contact us</a>.</p>
<p>Should you wish to report a complaint or if you feel that we have not addressed your concern in a satisfactory manner, you have the right to lodge a complaint with your local data protection authority.</p>
<h3>Electronic mailing lists and how to unsubscribe</h3>
<p>We may use your contact information periodically in order to send you email or other communications regarding updates to the Service and to alert you or remind you of happenings related to the Service. The frequency of these messages will vary depending upon various factors.</p>
<p>If you no longer wish to receive e-mails from us and want to be removed from our electronic mailing list, please <a href="/contact">contact us</a> and include “Email Unsubscribe” in the body of your message. If you choose to unsubscribe from our mailing lists, we will hold your contact information on file marked so that we do not inadvertently contact you if your details are subsequently provided to us by a third party.</p>
<h3>Data protection officer</h3>
<p>To communicate with our Data Protection Officer, please email <%= sails.config.custom.internalEmailAddress %>.</p>
<h3>Other provisions</h3>
<p>Your use of the Service indicates an acceptance of the terms of the Privacy Policy. We reserve the right to update or amend this Privacy Policy at any time. We will post any revised version of this Privacy Policy on our website, and we encourage you to refer back to it on a regular basis. The changes contained in any such revised version of this Privacy Policy will be effective from the time such revised version is posted unless otherwise specifically stated therein.</p>
<p>This Privacy Policy does not create or confer upon any individual any rights, or impose upon NEW_APP_COMPANY_NAME any rights or obligations outside of, or in addition to, any rights or obligations imposed by the privacy laws of such individuals jurisdiction, as applicable. Privacy legislation or an individuals right to privacy may differ from one jurisdiction to another. The rights and obligations described in this Privacy Policy may not apply to all individuals or in all jurisdictions. Should there be, in a specific case, any inconsistency between this Privacy Policy and the applicable privacy laws of such individuals jurisdiction, this Privacy Policy shall be interpreted, in respect of that case, to give effect to, and comply with, such privacy laws.</p>
<p>Please be aware that, by accessing our Service, you acknowledge that our servers are currently located in the United States and that the various communications necessarily result in the transfer of information across international boundaries. By accessing the Service and communicating electronically with us, you consent to the processing and transfer of your personal information as set out in this Privacy Policy. Personal information collected by us may be stored and processed in the United States of America or any other country in which we maintain facilities. Personal information collected by us may be transferred among our various divisions and affiliated companies around the world, which may involve transfer from countries within the European Economic Area to countries outside the European Economic Area. By choosing to access the Service, you consent to any such transfer, storage or processing of personal data outside of your country.</p>
<p>This Privacy Policy applies solely to the information collected by us on-line through the Service and does not apply to any website, application or service other than the Service. The Service contains links to other websites, applications and services provided by third parties that we do not own or control and we are not responsible for the privacy policies and practices of those third parties. Similarly, we are not responsible for the privacy policies and practices of any other websites, applications and services from which you linked to our Service. We encourage you to be aware when you leave our Service and to read the privacy policies of each and every third party that may be providing websites, applications or information linked to the Service.</p>
<p>This Privacy Policy may include examples but is not intended to be restricted in its application to such examples, thus including means including without limitation.</p>
<p>Your privacy is important to us. If you have questions about any of the provisions described above, please <a href="/contact">contact us</a>.</p>
<p><strong>Last updated: DATE_PRIVACY_POLICY_LAST_UPDATED</strong></p>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>

View File

@@ -0,0 +1,38 @@
<div id="terms">
<div class="container pt-5 pb-5">
<h1>Terms of service</h1>
<hr/>
<p>There are some simple ground rules you'll need to agree to before using NEW_APP_NAME. Playing by these rules is a mandatory part of using our application, so if you have any questions about them, please <a href="/contact">drop us a line</a> and we'll respond posthaste.</p>
<h3>NEW_APP_NAME agreement</h3>
<p>By using or signing up for NEW_APP_NAME, you are agreeing to be bound by the following terms and conditions (the “<strong>Agreement</strong>”). </p>
<p>By marking the applicable "I have read and agree to the terms of service" checkbox, you hereby agree to the Agreement.</p>
<h5>1. NEW_APP_NAME services</h5>
<p>In exchange for your payment of the applicable subscription fee, you may request NEW_APP_NAME services, as described on our website at <%= sails.config.custom.baseUrl %>. We will provide the NEW_APP_NAME services in accordance with this Agreement and the other terms and conditions described on our website at <%= sails.config.custom.baseUrl %>.</p>
<h5>2. NEW_APP_NAME account</h5>
<p>To become a customer, you must complete an order form and create an account by providing first name, last name and company name (if applicable), email address, and any other information indicated as required. We may reject any order form or application for an account for any reason, in our sole discretion. You acknowledge that we will use the email address provided by you as the primary method for communication. You are responsible for keeping your account password secure. We cannot and will not be liable for any loss or damage arising from any failure to maintain the security of the account and password.</p>
<p>If you place an order and sign up for an account on behalf of your employer, your employer will be deemed to be the customer for the purpose of this Agreement, and you represent and warrant that you have the authority to bind your employer to this Agreement. Each customer is responsible for assuring that its employees, agents and subcontractors comply with this Agreement.</p>
<h5>3. Fees</h5>
<p>You will pay a fee for your subscription as specified in your NEW_APP_NAME order form. Fees are based on the subscription term and not actual usage, and fees paid are non-refundable.</p>
<h5>4. Termination</h5>
<p>Either party may terminate this Agreement at any time, with or without cause, effective immediately upon written or electronic notice. Upon termination of this Agreement, all rights granted to you hereunder will immediately cease, including but not limited to the right to access the Account, unless otherwise determined in our sole discretion.</p>
<h5>5. Intellectual property rights</h5>
<p>You agree not to provide us with any confidential or proprietary information. Each party acknowledges and agrees that the other party may freely use and otherwise commercially exploit any ideas, feedback or suggestions provided by it to the other party.</p>
<h5>6. Disclaimers and limitation of liability of warranty</h5>
<p><strong>THE SERVICES ARE PROVIDED “AS-IS” AND WE EXPRESSLY DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE.</strong></p>
<p><strong>NEITHER PARTY WILL HAVE ANY LIABILITY WITH RESPECT TO THE SERVICES OR OBLIGATIONS UNDER THIS AGREEMENT OR OTHERWISE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR EXEMPLARY DAMAGES, INCLUDING BUT NOT LIMITED TO, DAMAGES FOR LOSSES OF PROFITS, GOODWILL, USE, DATA OR OTHER INTANGIBLE LOSSES RESULTING IN ANY WAY FROM THE SERVICES. IN ANY EVENT, EACH PARTYS LIABILITY UNDER THIS AGREEMENT FOR ANY REASON WILL BE LIMITED TO THE FEES PAID BY YOU DURING THE SIX MONTH PERIOD IMMEDIATELY PRECEDING THE EVENT GIVING RISE TO THE CLAIM FOR DAMAGES. THIS LIMITATION APPLIES TO ALL CAUSES OF ACTION IN THE AGGREGATE, INCLUDING, BUT NOT LIMITED TO, BREACH OF CONTRACT, BREACH OF WARRANTY, NEGLIGENCE, STRICT LIABILITY, MISREPRESENTATIONS, AND OTHER TORTS. THESE LIMITATIONS APPLY EVEN IF A PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THE FOREGOING LIMITATIONS APPLY TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW.</strong></p>
<h5>7. General provisions</h5>
<p>This Agreement sets forth the entire agreement and supersedes any and all prior agreements, written or oral, of the parties with respect to the subject matter hereof. We reserve the right to update and change the Agreement prospectively by posting updates and changes on our website, <%= sails.config.custom.baseUrl %>. You are advised to please check the Agreement from time to time for any updates or changes that may impact you. If a significant change is made, we will provide reasonable notice by email and an opportunity to terminate this Agreement if you do not agree with the change.</p>
<p>The parties to this Agreement are independent contractors. Neither party is an agent, representative or related entity of the other party. Nothing in this Agreement is intended to create, nor will it be construed as creating, any exclusive arrangement between the parties to this Agreement. This Agreement does not restrict either party from entering into similar arrangements with others. Each party understands that the other party may now or in the future be involved in developing or providing competitive products or services. Neither party is liability for any failure or delay caused by any circumstance or event beyond its reasonable control.</p>
<p><strong>Last updated: DATE_TERMS_OF_SERVICE_LAST_UPDATED</strong></p>
</div>
</div>
<%- /* Expose locals as `window.SAILS_LOCALS` :: */ exposeLocalsToBrowser() %>