nitro-web 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintrc.json +86 -0
  3. package/_example/.env-example +16 -0
  4. package/_example/client/config.ts +5 -0
  5. package/_example/client/css/index.css +35 -0
  6. package/_example/client/fonts/Roboto-Bold.ttf +0 -0
  7. package/_example/client/fonts/Roboto-BoldItalic.ttf +0 -0
  8. package/_example/client/fonts/Roboto-Italic.ttf +0 -0
  9. package/_example/client/fonts/Roboto-Medium.ttf +0 -0
  10. package/_example/client/fonts/Roboto-MediumItalic.ttf +0 -0
  11. package/_example/client/fonts/Roboto-Regular.ttf +0 -0
  12. package/_example/client/fonts/inter-v13-latin-300.woff2 +0 -0
  13. package/_example/client/fonts/inter-v13-latin-500.woff2 +0 -0
  14. package/_example/client/fonts/inter-v13-latin-600.woff2 +0 -0
  15. package/_example/client/fonts/inter-v13-latin-700.woff2 +0 -0
  16. package/_example/client/fonts/inter-v13-latin-800.woff2 +0 -0
  17. package/_example/client/fonts/inter-v13-latin-900.woff2 +0 -0
  18. package/_example/client/fonts/inter-v13-latin-regular.woff2 +0 -0
  19. package/_example/client/imgs/android-chrome-512x512.png +0 -0
  20. package/_example/client/imgs/favicon.png +0 -0
  21. package/_example/client/imgs/icons/calendar.svg +3 -0
  22. package/_example/client/imgs/icons/email.svg +6 -0
  23. package/_example/client/imgs/icons/eye-open.svg +4 -0
  24. package/_example/client/imgs/icons/eye.svg +5 -0
  25. package/_example/client/imgs/icons/filter.svg +7 -0
  26. package/_example/client/imgs/icons/left-circle.svg +3 -0
  27. package/_example/client/imgs/icons/left.svg +3 -0
  28. package/_example/client/imgs/icons/line-options.svg +5 -0
  29. package/_example/client/imgs/icons/line.svg +3 -0
  30. package/_example/client/imgs/icons/person.svg +7 -0
  31. package/_example/client/imgs/icons/plus-circle.svg +5 -0
  32. package/_example/client/imgs/icons/plus.svg +5 -0
  33. package/_example/client/imgs/icons/right-circle.svg +3 -0
  34. package/_example/client/imgs/icons/right.svg +3 -0
  35. package/_example/client/imgs/icons/search.svg +3 -0
  36. package/_example/client/imgs/icons/shield.svg +6 -0
  37. package/_example/client/imgs/icons/tick-circle-solid.svg +8 -0
  38. package/_example/client/imgs/icons/tick-circle.svg +6 -0
  39. package/_example/client/imgs/icons/tick.svg +5 -0
  40. package/_example/client/imgs/icons/up2-small.svg +4 -0
  41. package/_example/client/imgs/icons/up2.svg +4 -0
  42. package/_example/client/imgs/icons/updown.svg +6 -0
  43. package/_example/client/imgs/icons/v-big-dark.svg +3 -0
  44. package/_example/client/imgs/icons/v-dark.svg +3 -0
  45. package/_example/client/imgs/icons/v.svg +3 -0
  46. package/_example/client/imgs/icons/v2-active.svg +6 -0
  47. package/_example/client/imgs/icons/x1.svg +4 -0
  48. package/_example/client/imgs/logo/logo-white.svg +20 -0
  49. package/_example/client/imgs/logo/logo.svg +20 -0
  50. package/_example/client/imgs/no-image.jpg +0 -0
  51. package/_example/client/imgs/user.jpg +0 -0
  52. package/_example/client/index.html +12 -0
  53. package/_example/client/index.ts +47 -0
  54. package/_example/components/auth.api.js +1 -0
  55. package/_example/components/index.tsx +225 -0
  56. package/_example/components/partials/layouts.tsx +5 -0
  57. package/_example/components/settings.api.js +1 -0
  58. package/_example/server/config.js +120 -0
  59. package/_example/server/email/welcome.html +27 -0
  60. package/_example/server/index.js +32 -0
  61. package/_example/tailwind.config.js +84 -0
  62. package/_example/tsconfig.json +32 -0
  63. package/_example/types.d.ts +7 -0
  64. package/_example/webpack.config.js +4 -0
  65. package/client/app.js +300 -0
  66. package/client/css/components.css +84 -0
  67. package/client/css/fonts.css +67 -0
  68. package/client/imgs/icons/calendar.svg +3 -0
  69. package/client/imgs/icons/email.svg +6 -0
  70. package/client/imgs/icons/eye-open.svg +4 -0
  71. package/client/imgs/icons/eye.svg +5 -0
  72. package/client/imgs/icons/filter.svg +7 -0
  73. package/client/imgs/icons/left-circle.svg +3 -0
  74. package/client/imgs/icons/left.svg +3 -0
  75. package/client/imgs/icons/line-options.svg +5 -0
  76. package/client/imgs/icons/line.svg +3 -0
  77. package/client/imgs/icons/person.svg +7 -0
  78. package/client/imgs/icons/plus-circle.svg +5 -0
  79. package/client/imgs/icons/plus.svg +5 -0
  80. package/client/imgs/icons/right-circle.svg +3 -0
  81. package/client/imgs/icons/right.svg +3 -0
  82. package/client/imgs/icons/search.svg +3 -0
  83. package/client/imgs/icons/shield.svg +6 -0
  84. package/client/imgs/icons/tick-circle-solid.svg +8 -0
  85. package/client/imgs/icons/tick-circle.svg +6 -0
  86. package/client/imgs/icons/tick.svg +5 -0
  87. package/client/imgs/icons/up2-small.svg +4 -0
  88. package/client/imgs/icons/up2.svg +4 -0
  89. package/client/imgs/icons/updown.svg +6 -0
  90. package/client/imgs/icons/v-big-dark.svg +3 -0
  91. package/client/imgs/icons/v-dark.svg +3 -0
  92. package/client/imgs/icons/v.svg +3 -0
  93. package/client/imgs/icons/v2-active.svg +6 -0
  94. package/client/imgs/icons/x1.svg +4 -0
  95. package/client.js +42 -0
  96. package/components/auth/auth.api.js +419 -0
  97. package/components/auth/reset.jsx +88 -0
  98. package/components/auth/signin.jsx +74 -0
  99. package/components/auth/signup.jsx +62 -0
  100. package/components/billing/stripe.api.js +267 -0
  101. package/components/partials/element/accordion.jsx +82 -0
  102. package/components/partials/element/avatar.jsx +28 -0
  103. package/components/partials/element/button.jsx +66 -0
  104. package/components/partials/element/dropdown.jsx +185 -0
  105. package/components/partials/element/initials.jsx +56 -0
  106. package/components/partials/element/message.jsx +124 -0
  107. package/components/partials/element/modal.jsx +229 -0
  108. package/components/partials/element/sidebar.jsx +166 -0
  109. package/components/partials/element/tooltip.jsx +146 -0
  110. package/components/partials/element/topbar.jsx +25 -0
  111. package/components/partials/form/checkbox.jsx +74 -0
  112. package/components/partials/form/drop-handler.jsx +62 -0
  113. package/components/partials/form/drop.jsx +125 -0
  114. package/components/partials/form/form-error.jsx +21 -0
  115. package/components/partials/form/input-color.jsx +77 -0
  116. package/components/partials/form/input-currency.jsx +133 -0
  117. package/components/partials/form/input-date.jsx +223 -0
  118. package/components/partials/form/input.jsx +131 -0
  119. package/components/partials/form/location.jsx +212 -0
  120. package/components/partials/form/select.jsx +369 -0
  121. package/components/partials/form/toggle.jsx +46 -0
  122. package/components/partials/is-first-render.js +15 -0
  123. package/components/partials/layout/layout1.jsx +32 -0
  124. package/components/partials/layout/layout2.jsx +47 -0
  125. package/components/partials/not-found.jsx +7 -0
  126. package/components/partials/styleguide.jsx +252 -0
  127. package/components/settings/settings-account.jsx +143 -0
  128. package/components/settings/settings-business.jsx +121 -0
  129. package/components/settings/settings-team--member.jsx +108 -0
  130. package/components/settings/settings-team.jsx +76 -0
  131. package/components/settings/settings.api.js +54 -0
  132. package/package.json +175 -0
  133. package/readme.md +43 -0
  134. package/server/email/index.js +192 -0
  135. package/server/email/partials/email.css +153 -0
  136. package/server/email/partials/layout1.swig +92 -0
  137. package/server/email/partials/line.swig +8 -0
  138. package/server/email/partials/vert-10.swig +8 -0
  139. package/server/email/partials/vert-15.swig +8 -0
  140. package/server/email/partials/vert-20.swig +8 -0
  141. package/server/email/partials/vert-25.swig +8 -0
  142. package/server/email/partials/vert-30.swig +8 -0
  143. package/server/email/partials/vert-35.swig +8 -0
  144. package/server/email/partials/vert-50.swig +8 -0
  145. package/server/email/reset-password.html +21 -0
  146. package/server/email/welcome.html +21 -0
  147. package/server/models/company.js +76 -0
  148. package/server/models/user.js +45 -0
  149. package/server/router.js +355 -0
  150. package/server.js +20 -0
  151. package/util.js +1145 -0
  152. package/webpack.config.js +302 -0
@@ -0,0 +1,225 @@
1
+ /*
2
+ * Any component with a .route property is called a page component. Any page component found under /components
3
+ * will be automatically imported and setup by the client router, we're just listing them all here for the sake of example.
4
+ */
5
+ import {
6
+ Signin,
7
+ Signup,
8
+ ResetInstructions,
9
+ ResetPassword,
10
+ // SettingsAccount,
11
+ // SettingsBusiness,
12
+ // SettingsTeam,
13
+ Styleguide,
14
+ NotFound,
15
+ } from 'nitro-web'
16
+ import config from '../client/config'
17
+
18
+ // Signin page (can be saved onto a seperate file under the components folder)
19
+ export const SigninPage = () => <Signin config={config} />
20
+ SigninPage.route = {
21
+ '/signin': true,
22
+ '/signout': true,
23
+ 'meta': { 'title': 'Sign In - Nitro', layout: 2 },
24
+ }
25
+
26
+ // Signup page
27
+ export const SignupPage = () => <Signup config={config} />
28
+ SignupPage.route = {
29
+ '/signup': true,
30
+ 'meta': { 'title': 'Sign Up - Nitro', layout: 2 },
31
+ }
32
+
33
+ // Reset instructions page
34
+ export const ResetInstructionsPage = () => <ResetInstructions />
35
+ ResetInstructionsPage.route = {
36
+ '/reset': true,
37
+ 'meta': { 'title': 'Reset password - Nitro', layout: 2 },
38
+ }
39
+
40
+ // Reset password page
41
+ export const ResetPasswordPage = () => <ResetPassword />
42
+ ResetPasswordPage.route = {
43
+ '/reset/:token': true,
44
+ 'meta': { 'title': 'Reset password - Nitro', layout: 2 },
45
+ }
46
+
47
+ // // Settings Account page
48
+ // export const SettingsAccountPage = () => <SettingsAccount />
49
+ // SettingsAccountPage.route = {
50
+ // '/settings/account': ['isUser'],
51
+ // 'meta': { 'title': 'Account Settings - Nitro', layout: 1 },
52
+ // }
53
+
54
+ // // Settings Business page
55
+ // export const SettingsBusinessPage = () => <SettingsBusiness config={config} />
56
+ // SettingsBusinessPage.route = {
57
+ // '/settings/business': ['isUser'],
58
+ // 'meta': { 'title': 'Business Settings - Nitro', layout: 1 },
59
+ // }
60
+
61
+ // // Settings Team page
62
+ // export const SettingsTeamPage = () => <SettingsTeam config={config} />
63
+ // SettingsTeamPage.route = {
64
+ // '/settings/team': ['isUser'],
65
+ // 'meta': { 'title': 'Team Settings - Nitro', layout: 1 },
66
+ // }
67
+
68
+ // Dashboard page
69
+ export function DashboardPage() {
70
+ return (
71
+ <div>
72
+ <h1 className="h1 mb-8">Dashboard</h1>
73
+ <p className="text-gray-700">
74
+ Welcome to Nitro, a modular React/Express base project, styled using Tailwind 🚀.
75
+ </p>
76
+ </div>
77
+ )
78
+ }
79
+ DashboardPage.route = {
80
+ '/': true,
81
+ 'meta': { 'title': 'Dashboard - Nitro', layout: 1 },
82
+ }
83
+
84
+ // Styleguide page
85
+ export const StyleguidePage = () => <Styleguide config={config} />
86
+ StyleguidePage.route = {
87
+ '/styleguide': true,
88
+ 'meta': { title: 'Style Guide - Nitro', layout: 1 },
89
+ }
90
+
91
+ // Not found page
92
+ export const NotFoundPage = () => <NotFound />
93
+ NotFoundPage.route = {
94
+ '*': true,
95
+ 'meta': { 'title': 'Nothing found - Nitro', layout: 1 },
96
+ }
97
+
98
+ // Tailwind UI pricing page example
99
+ export function PricingPage() {
100
+ const tiers = [
101
+ {
102
+ name: 'Hobby',
103
+ id: 'tier-hobby',
104
+ href: '#',
105
+ priceMonthly: '$29',
106
+ description: 'The perfect plan if you\'re just getting started with our product.',
107
+ features: ['25 products', 'Up to 10,000 subscribers', 'Advanced analytics', '24-hour support response time'],
108
+ featured: false,
109
+ },
110
+ {
111
+ name: 'Enterprise',
112
+ id: 'tier-enterprise',
113
+ href: '#',
114
+ priceMonthly: '$99',
115
+ description: 'Dedicated support and infrastructure for your company.',
116
+ features: [
117
+ 'Unlimited products',
118
+ 'Unlimited subscribers',
119
+ 'Advanced analytics',
120
+ 'Dedicated support representative',
121
+ 'Marketing automations',
122
+ 'Custom integrations',
123
+ ],
124
+ featured: true,
125
+ },
126
+ ]
127
+ function classNames(...classes: string[]) {
128
+ return classes.filter(Boolean).join(' ')
129
+ }
130
+ return (
131
+ <div className="relative isolate bg-white py-12 md:py-16 px-6">
132
+ <div aria-hidden="true" className="absolute inset-x-0 -top-3 -z-10 transform-gpu overflow-hidden px-36 blur-3xl">
133
+ <div
134
+ style={{
135
+ clipPath:
136
+ 'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%,'
137
+ +' 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
138
+ }}
139
+ className="mx-auto aspect-[1155/678] w-[72.1875rem] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-30"
140
+ />
141
+ </div>
142
+ <div className="mx-auto max-w-4xl text-center">
143
+ <h2 className="text-base/7 font-semibold text-indigo-600">Pricing</h2>
144
+ <p className="mt-2 text-balance text-5xl font-semibold tracking-tight text-gray-900 sm:text-5xl">
145
+ Choose the right plan for you
146
+ </p>
147
+ </div>
148
+ <p className="mx-auto mt-6 max-w-2xl text-pretty text-center text-lg font-medium text-gray-600 sm:text-xl/8">
149
+ Choose an affordable plan that’s packed with the best features for engaging your audience, creating customer
150
+ loyalty, and driving sales.
151
+ </p>
152
+ <div className="mx-auto mt-16 grid max-w-lg grid-cols-1 items-center gap-y-6 sm:mt-20 sm:gap-y-0 lg:max-w-4xl lg:grid-cols-2">
153
+ {tiers.map((tier, tierIdx) => (
154
+ <div
155
+ key={tier.id}
156
+ className={classNames(
157
+ tier.featured ? 'relative bg-gray-900 shadow-2xl' : 'bg-white/60 sm:mx-8 lg:mx-0',
158
+ tier.featured
159
+ ? ''
160
+ : tierIdx === 0
161
+ ? 'rounded-t-3xl sm:rounded-b-none lg:rounded-bl-3xl lg:rounded-tr-none'
162
+ : 'sm:rounded-t-none lg:rounded-bl-none lg:rounded-tr-3xl',
163
+ 'rounded-3xl p-8 ring-1 ring-gray-900/10 sm:p-10'
164
+ )}
165
+ >
166
+ <h3
167
+ id={tier.id}
168
+ className={classNames(tier.featured ? 'text-indigo-400' : 'text-indigo-600', 'text-base/7 font-semibold')}
169
+ >
170
+ {tier.name}
171
+ </h3>
172
+ <p className="mt-4 flex items-baseline gap-x-2">
173
+ <span
174
+ className={classNames(
175
+ tier.featured ? 'text-white' : 'text-gray-900',
176
+ 'text-5xl font-semibold tracking-tight'
177
+ )}
178
+ >
179
+ {tier.priceMonthly}
180
+ </span>
181
+ <span className={classNames(tier.featured ? 'text-gray-400' : 'text-gray-500', 'text-base')}>/month</span>
182
+ </p>
183
+ <p className={classNames(tier.featured ? 'text-gray-300' : 'text-gray-600', 'mt-6 text-base/7')}>
184
+ {tier.description}
185
+ </p>
186
+ <ul
187
+ role="list"
188
+ className={classNames(
189
+ tier.featured ? 'text-gray-300' : 'text-gray-600',
190
+ 'mt-8 space-y-3 text-sm/6 sm:mt-10'
191
+ )}
192
+ >
193
+ {tier.features.map((feature) => (
194
+ <li key={feature} className="flex gap-x-3">
195
+ {/* <CheckIcon
196
+ aria-hidden="true"
197
+ className={classNames(tier.featured ? 'text-indigo-400' : 'text-indigo-600', 'h-6 w-5 flex-none')}
198
+ /> */}
199
+ {feature}
200
+ </li>
201
+ ))}
202
+ </ul>
203
+ <a
204
+ href={tier.href}
205
+ aria-describedby={tier.id}
206
+ className={classNames(
207
+ tier.featured
208
+ ? 'bg-indigo-500 text-white shadow-sm hover:bg-indigo-400 focus-visible:outline-indigo-500'
209
+ : 'text-indigo-600 ring-1 ring-inset ring-indigo-200 hover:ring-indigo-300 focus-visible:outline-indigo-600',
210
+ 'mt-8 block rounded-md px-3.5 py-2.5 text-center text-sm font-semibold focus-visible:outline '
211
+ + 'focus-visible:outline-2 focus-visible:outline-offset-2 sm:mt-10'
212
+ )}
213
+ >
214
+ Get started today
215
+ </a>
216
+ </div>
217
+ ))}
218
+ </div>
219
+ </div>
220
+ )
221
+ }
222
+ PricingPage.route = {
223
+ '/pricing': true,
224
+ 'meta': { 'title': 'Pricing - Nitro', layout: 1 },
225
+ }
@@ -0,0 +1,5 @@
1
+ import { Layout1 as L1, Layout2 as L2 } from 'nitro-web'
2
+ import Logo from '../../client/imgs/logo/logo.svg'
3
+
4
+ export const Layout1 = () => { return <L1 Logo={Logo} /> }
5
+ export const Layout2 = () => { return <L2 Logo={Logo} /> }
@@ -0,0 +1 @@
1
+ export { settings as default } from '#nitro-web/server.js'
@@ -0,0 +1,120 @@
1
+ import 'dotenv/config'
2
+ import { dirname, resolve } from 'path'
3
+ import { fileURLToPath } from 'url'
4
+ import { createRequire } from 'module'
5
+ const _require = createRequire(import.meta.url)
6
+ const projectDir = resolve(dirname(fileURLToPath(import.meta.url)) + '/../') + '/'
7
+ const env = process.env.env || (process.env.NODE_ENV !== 'production' ? 'development' : process.env.NODE_ENV)
8
+ const isNitro = process.env.NITRO
9
+
10
+ export default {
11
+ inject: 'awsUrl clientUrl currencies countries env googleMapsApiKey stripePublishableKey testEmail',
12
+
13
+ // apiUrl: process.env.originUrl || 'http://localhost:3001',
14
+ clientUrl: process.env.originUrl || 'http://localhost:3000',
15
+ emailFrom: process.env.emailFrom,
16
+ emailReplyTo: process.env.emailReplyTo,
17
+ emailTestMode: process.env.emailTestMode,
18
+ env: env,
19
+ masterPassword: process.env.masterPassword,
20
+ mongoUrl: process.env.mongoUrl,
21
+ testEmail: process.env.testEmail,
22
+
23
+ clientDir: projectDir + 'client/',
24
+ componentsDir: projectDir + 'components/',
25
+ distDir: projectDir + 'client/dist/',
26
+ emailTemplateDir: projectDir + 'server/email/',
27
+ modelsDir: projectDir + 'server/models/',
28
+ projectDir: projectDir,
29
+ tmpDir: projectDir + 'tmp/',
30
+ nitroDir: resolve(isNitro ? projectDir + '../' : projectDir + 'node_modules/nitro-web/') + '/',
31
+
32
+ awsUrl: process.env.awsUrl,
33
+ googleMapsApiKey: process.env.googleMapsApiKey,
34
+ mailgunDomain: process.env.mailgunDomain,
35
+ mailgunKey: process.env.mailgunKey,
36
+ stripePublishableKey: process.env.stripePublishableKey,
37
+ stripeSecretKey: process.env.stripeSecretKey,
38
+ stripeWebhookSecret: process.env.stripeWebhookSecret,
39
+
40
+ monasteryOptions: {
41
+ noDefaults: true,
42
+ nullObjects: true,
43
+ useMilliseconds: true,
44
+ imagePlugin: process.env.awsSecretAccessKey
45
+ ? {
46
+ awsBucket: process.env.awsBucket,
47
+ awsRegion: process.env.awsRegion,
48
+ awsAccessKeyId: process.env.awsAccessKeyId,
49
+ awsSecretAccessKey: process.env.awsSecretAccessKey,
50
+ formats: ['png', 'jpg', 'jpeg', 'bmp', 'tiff', 'gif', 'webp'],
51
+ }
52
+ : undefined,
53
+ // show mongod selection error faster in development
54
+ serverSelectionTimeoutMS: env == 'development' ? 3000 : undefined,
55
+ },
56
+
57
+ countries: {
58
+ nz: {
59
+ currency: 'nzd',
60
+ name: 'New Zealand',
61
+ numberFormats: {
62
+ currency: '¤#,##0.00',
63
+ percentage: '¤#,##0.00%',
64
+ },
65
+ dateFormats: {
66
+ full: 'dddd, D MMMM YYYY',
67
+ long: 'D MMMM YYYY',
68
+ medium: 'D/MM/YYYY',
69
+ short: 'D/MM/YY',
70
+ },
71
+ },
72
+ },
73
+
74
+ currencies: {
75
+ nzd: {
76
+ name: 'New Zealand Dollar',
77
+ symbol: '$',
78
+ },
79
+ },
80
+
81
+ middleware: {
82
+ isAdmin: (req, res, next) => {
83
+ // Still need to remove cookie matching in favour of uid..
84
+ // E.g. Cookie matching handy for rare issues, e.g. signout > signin (to a different user on another tab)
85
+ let cookieMatch = req.user && (!req.headers.userid || req.user._id.toString() == req.headers.userid)
86
+ if (cookieMatch && req.user.type.match(/admin/)) next()
87
+ else if (req.user && req.user.type.match(/admin/)) res.unauthorized('Invalid cookie, please refresh your browser')
88
+ else if (req.user) res.unauthorized('You are not authorised to make this request.')
89
+ else res.unauthorized('Please sign in first.')
90
+ },
91
+ isCompanyOwner: (req, res, next) => {
92
+ let user = req.user || { companies: [] }
93
+ let cid = req.params.cid
94
+ let company = user.companies.find((o) => o._id.toString() == cid)
95
+ let companyUser = company?.users?.find((o) => o._id.toString() == user._id.toString())
96
+ if (!user._id) return res.unauthorized('Please sign in first.')
97
+ else if (!company || !companyUser) res.unauthorized('You are not authorised to make this request.')
98
+ else if (companyUser.type != 'owner') res.unauthorized('Only owners can make this request.')
99
+ else next()
100
+ },
101
+ isCompanyUser: (req, res, next) => {
102
+ let user = req.user || { companies: [] }
103
+ let cid = req.params.cid
104
+ let company = user.companies.find((o) => o._id.toString() == cid)
105
+ if (!user._id) return res.unauthorized('Please sign in first.')
106
+ else if (!company) res.unauthorized('You are not authorised to make this request.')
107
+ else next()
108
+ },
109
+ isUser: (req, res, next) => {
110
+ // todo: need to double check that uid is always defined
111
+ let uid = req.params.uid
112
+ if (req.user && (typeof uid == 'undefined' || req.user._id.toString() == uid)) next()
113
+ else if (req.user) res.unauthorized('You are not authorised to make this request.')
114
+ else res.unauthorized('Please sign in first.')
115
+ },
116
+ },
117
+
118
+ version: _require(projectDir + (isNitro ? '../' : '') + 'package.json').version,
119
+ isNitro: isNitro,
120
+ }
@@ -0,0 +1,27 @@
1
+ <!-- You can view templates at http://localhost:3001/email/welcome -->
2
+ <!-- To modify the css, add your own /server/email/partials/email.css file -->
3
+
4
+ <!-- extends references the default nitro layout file -->
5
+ {% extends "partials/layout1.swig" %}
6
+
7
+ <!-- block content is the content of the email -->
8
+ {% block content %}
9
+
10
+ [[ subject = Welcome to Nitro ]]
11
+ <b>%recipient.greet%</b>,<br/>
12
+ <br/>
13
+ Thanks for trying out Nitro (the example)! <br/>
14
+ <br/>
15
+ If you have any feature requests, feedback or questions, please don't hesitate to reach out to us at <a href="mailto:%recipient.replyToEmail%">%recipient.replyToEmail%</a>.<br/>
16
+ <br/>
17
+ <br/>
18
+ <span mc:edit="button">
19
+ <a class="button" href="%recipient.domain%/signin?email=%recipient.email%" target="_blank">
20
+ Account Sign In
21
+ </a>
22
+ </span><br/>
23
+ <br/>
24
+ Thanks,<br/>
25
+ <b>The Nitro Team</b>
26
+
27
+ {% endblock %}
@@ -0,0 +1,32 @@
1
+ import 'dotenv/config'
2
+ import db from 'monastery'
3
+ import config from './config.js'
4
+ import { setupRouter, setupDefaultModels } from '#nitro-web/server.js'
5
+
6
+ // Setup monastery models
7
+ db.manager(config.mongoUrl, config.monasteryOptions)
8
+ await db.models(config.modelsDir)
9
+ await setupDefaultModels(db)
10
+
11
+ // Catch mongod not running
12
+ if (config.env === 'development') {
13
+ db.onError((err) => console.log(err))
14
+ }
15
+
16
+ // Setup router
17
+ const server = await setupRouter(config)
18
+
19
+ // Start express
20
+ server.listen(process.env.PORT || 3001, '0.0.0.0', async () => {
21
+ // ...success
22
+ })
23
+
24
+ // You can send emails like this:
25
+ // const html = await sendEmail({
26
+ // config: config,
27
+ // data: { name: 'Test' },
28
+ // template: 'welcome',
29
+ // test: true,
30
+ // to: 'test@test.com',
31
+ // })
32
+ // console.log(html)
@@ -0,0 +1,84 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ // https://github.com/tailwindlabs/tailwindcss/blob/main/stubs/config.full.js#L889
3
+ import defaultTheme from 'tailwindcss/defaultTheme'
4
+ import colors from 'tailwindcss/colors'
5
+ import { nitroDir } from './server/config'
6
+ import Color from 'color'
7
+ const lighten = (clr, val) => Color(clr).lighten(val).rgb().string()
8
+
9
+ export default {
10
+ content: {
11
+ relative: true,
12
+ files: [
13
+ './components/**/*.{ts,tsx}',
14
+ nitroDir + 'components/**/*.{js,jsx}',
15
+ ],
16
+ },
17
+ experimental: {
18
+ optimizeUniversalDefaults: true, // remove unneeded varables from universal selectors
19
+ },
20
+ theme: {
21
+ extend: {
22
+ // Nitro theme variables
23
+ boxShadow: {
24
+ 'dropdown-ul': '0 2px 8px 0 rgba(0, 0, 0, 0.05)',
25
+ },
26
+ colors: {
27
+ // Main colors
28
+ 'primary': colors.indigo[500],
29
+ 'primary-dark': colors.indigo[600],
30
+ 'primary-light': colors.indigo[400],
31
+ 'primary-hover': lighten(colors.indigo[500], 0.05),
32
+ 'secondary': colors.green[500],
33
+ 'secondary-dark': colors.green[600],
34
+ 'secondary-light': colors.green[400],
35
+ 'secondary-hover': lighten(colors.green[500], 0.05),
36
+ 'label': colors.gray[900],
37
+ 'link': colors.black,
38
+ 'link-hover': colors.blue[200],
39
+ 'link-focus': colors.blue[200],
40
+ 'light': colors.gray[100],
41
+ 'dark': colors.gray[900],
42
+ // Alert colors
43
+ 'danger': '#ff0000',
44
+ 'danger-dark': colors.red[800],
45
+ 'info': colors.blue[500],
46
+ 'success': colors.green[500],
47
+ // Element colors
48
+ 'input': colors.gray[900],
49
+ 'input-placeholder': colors.gray[400],
50
+ 'input-border': colors.gray[300],
51
+ 'dropdown-ul-border': colors.gray[200],
52
+ },
53
+ fontFamily: {
54
+ sans: ['Inter', ...defaultTheme.fontFamily.sans],
55
+ },
56
+ fontSize: {
57
+ // '2xs': ['0.75rem', { lineHeight: '1.5' }],
58
+ // 'xs': ['0.81rem', { lineHeight: '1.5' }],
59
+ // 'sm': ['0.875rem', { lineHeight: '1.5' }],
60
+ // 'base': ['1rem', { lineHeight: '1.5' }],
61
+ // 'lg': ['1.125rem', { lineHeight: '1.75' }],
62
+ // 'xl': ['1.25rem', { lineHeight: '1.75' }],
63
+ // '2xl': ['1.4rem', { lineHeight: '1.75' }],
64
+ // '3xl': ['1.875rem', { lineHeight: '1.75' }],
65
+
66
+ '2xs': ['0.77rem', { lineHeight: '1.5' }], // ~12px
67
+ 'xs': ['0.83rem', { lineHeight: '1.5' }], // ~13px
68
+ 'sm-label': ['0.87rem', { lineHeight: '1.5' }], // ~13.5px
69
+ 'sm': ['0.90rem', { lineHeight: '1.5' }], // ~14px
70
+ 'base': ['1rem', { lineHeight: '1.5' }], // 15.5px
71
+ 'lg': ['1.16rem', { lineHeight: '1.75' }], // ~18px
72
+ 'xl': ['1.29rem', { lineHeight: '1.75' }], // ~20px
73
+ '2xl': ['1.45rem', { lineHeight: '1.75' }], // ~22.4px
74
+ '3xl': ['1.94rem', { lineHeight: '1.75' }], // ~30px
75
+ },
76
+ spacing: {
77
+ 'input-before': '0.625rem',
78
+ 'input-after': '1.5rem',
79
+ },
80
+ },
81
+ },
82
+ plugins: [],
83
+ }
84
+
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "allowSyntheticDefaultImports": true,
5
+ "downlevelIteration": true,
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "isolatedModules": true,
9
+ "jsx": "react-jsx",
10
+ "lib": ["es6", "dom", "dom.iterable", "esnext"],
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "noEmit": false,
14
+ "noFallthroughCasesInSwitch": true,
15
+ "noImplicitAny": true,
16
+ "noImplicitReturns": false,
17
+ "noImplicitThis": true,
18
+ "noStrictGenericChecks": false,
19
+ "outDir": "./dist",
20
+ "paths": {
21
+ "nitro-web": [
22
+ "../client.js"
23
+ ],
24
+ },
25
+ "resolveJsonModule": true,
26
+ "skipLibCheck": true,
27
+ "sourceMap": true,
28
+ "strict": true,
29
+ "target": "es6",
30
+ },
31
+ "include": ["client", "components", "types.d.ts"],
32
+ }
@@ -0,0 +1,7 @@
1
+ declare module '*.svg' {
2
+ const content: string | React.FC<React.SVGProps<SVGElement>>
3
+ export default content
4
+ }
5
+ declare global {
6
+ const sharedStore: any
7
+ }
@@ -0,0 +1,4 @@
1
+ import config from './server/config.js'
2
+ import { getWebpackConfig } from '#nitro-web/webpack.config.js'
3
+
4
+ export default getWebpackConfig(config)