Merge pull request #390 from Irev-Dev/main
release 29th June 2021
This commit was merged in pull request #390.
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Hutten"
|
"Hutten",
|
||||||
|
"sendmail"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,3 +17,14 @@ CLOUDINARY_API_KEY=476712943135152
|
|||||||
# See: https://redwoodjs.com/docs/logger for level options:
|
# See: https://redwoodjs.com/docs/logger for level options:
|
||||||
# trace | info | debug | warn | error | silent
|
# trace | info | debug | warn | error | silent
|
||||||
# LOG_LEVEL=debug
|
# LOG_LEVEL=debug
|
||||||
|
|
||||||
|
|
||||||
|
# EMAIL_PASSWORD=abc123
|
||||||
|
|
||||||
|
|
||||||
|
CAD_LAMBDA_BASE_URL="http://localhost:8080"
|
||||||
|
|
||||||
|
# sentry
|
||||||
|
GITHUB_ASSIST_APP_ID=23342
|
||||||
|
GITHUB_ASSIST_SECRET=abc
|
||||||
|
GITHUB_ASSIST_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nabcdefg\n-----END RSA PRIVATE KEY-----"
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redwoodjs/api": "^0.34.1",
|
"@redwoodjs/api": "^0.34.1",
|
||||||
"@sentry/node": "^6.5.1",
|
"@sentry/node": "^6.5.1",
|
||||||
"cloudinary": "^1.23.0"
|
"cloudinary": "^1.23.0",
|
||||||
|
"nodemailer": "^6.6.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/nodemailer": "^6.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ Because of the way the docker containers to be deployed as lambdas on aws are so
|
|||||||
|
|
||||||
The docker build relies on a git ignored file, the aws-lambda-rie. [Download it](https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.0/aws-lambda-rie), then put it into `app/api/src/docker/common/`. alternatively you can put this download into the DockerFiles by reading the instructions at around line 29 of the DockerFiles (`app/api/src/docker/openscad/Dockerfile` & `app/api/src/docker/cadquery/Dockerfile`). However this will mean slower build times as it will need download this 14mb file every build.
|
The docker build relies on a git ignored file, the aws-lambda-rie. [Download it](https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.0/aws-lambda-rie), then put it into `app/api/src/docker/common/`. alternatively you can put this download into the DockerFiles by reading the instructions at around line 29 of the DockerFiles (`app/api/src/docker/openscad/Dockerfile` & `app/api/src/docker/cadquery/Dockerfile`). However this will mean slower build times as it will need download this 14mb file every build.
|
||||||
|
|
||||||
|
you will also need to create a .env in `app/api/src/docker/.env` for the following env-vars `DEV_AWS_SECRET_ACCESS_KEY, DEV_AWS_ACCESS_KEY_ID and DEV_BUCKET`. Ask @irev-dev for credentials and he can sort you out.
|
||||||
|
|
||||||
Then cd into this folder `cd api/src/docker` and:
|
Then cd into this folder `cd api/src/docker` and:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -10,19 +10,22 @@ app.use(cors())
|
|||||||
const invocationURL = (port) =>
|
const invocationURL = (port) =>
|
||||||
`http://localhost:${port}/2015-03-31/functions/function/invocations`
|
`http://localhost:${port}/2015-03-31/functions/function/invocations`
|
||||||
|
|
||||||
const makeRequest = (route, port) => [route, async (req, res) => {
|
const makeRequest = (route, port) => [
|
||||||
console.log(`making post request to ${port}, ${route}`)
|
route,
|
||||||
try {
|
async (req, res) => {
|
||||||
const { data } = await axios.post(invocationURL(port), {
|
console.log(`making post request to ${port}, ${route}`)
|
||||||
body: JSON.stringify(req.body),
|
try {
|
||||||
})
|
const { data } = await axios.post(invocationURL(port), {
|
||||||
res.status(data.statusCode)
|
body: JSON.stringify(req.body),
|
||||||
res.send(data.body)
|
})
|
||||||
} catch (e) {
|
res.status(data.statusCode)
|
||||||
res.status(500)
|
res.send(data.body)
|
||||||
res.send()
|
} catch (e) {
|
||||||
}
|
res.status(500)
|
||||||
}]
|
res.send()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
app.post(...makeRequest('/openscad/preview', 5052))
|
app.post(...makeRequest('/openscad/preview', 5052))
|
||||||
app.post(...makeRequest('/openscad/stl', 5053))
|
app.post(...makeRequest('/openscad/stl', 5053))
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
const { makeFile, runCommand } = require('../common/utils')
|
const { makeFile, runCommand } = require('../common/utils')
|
||||||
const { nanoid } = require('nanoid')
|
const { nanoid } = require('nanoid')
|
||||||
|
|
||||||
module.exports.runCQ = async ({ file, settings: {
|
module.exports.runCQ = async ({
|
||||||
deflection = 0.3
|
file,
|
||||||
} = {} } = {}) => {
|
settings: { deflection = 0.3 } = {},
|
||||||
|
} = {}) => {
|
||||||
const tempFile = await makeFile(file, '.py', nanoid)
|
const tempFile = await makeFile(file, '.py', nanoid)
|
||||||
const fullPath = `/tmp/${tempFile}/output.stl`
|
const fullPath = `/tmp/${tempFile}/output.stl`
|
||||||
const command = `cq-cli/cq-cli --codec stl --infile /tmp/${tempFile}/main.py --outfile ${fullPath} --outputopts "deflection:${deflection};angularDeflection:${deflection};"`
|
const command = `cq-cli/cq-cli --codec stl --infile /tmp/${tempFile}/main.py --outfile ${fullPath} --outputopts "deflection:${deflection};angularDeflection:${deflection};"`
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
services:
|
services:
|
||||||
# aws-emulator:
|
|
||||||
# build: .
|
|
||||||
# networks:
|
|
||||||
# - awsland
|
|
||||||
# ports:
|
|
||||||
# - "5050:8080"
|
|
||||||
|
|
||||||
openscad-health:
|
openscad-health:
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./openscad/.
|
dockerfile: ./openscad/Dockerfile
|
||||||
image: openscad
|
image: openscad
|
||||||
command: openscad.health
|
command: openscad.health
|
||||||
ports:
|
ports:
|
||||||
@@ -17,10 +11,7 @@ services:
|
|||||||
|
|
||||||
openscad-preview:
|
openscad-preview:
|
||||||
image: openscad
|
image: openscad
|
||||||
# build: ./openscad/.
|
|
||||||
command: openscad.preview
|
command: openscad.preview
|
||||||
# networks:
|
|
||||||
# - awsland
|
|
||||||
ports:
|
ports:
|
||||||
- "5052:8080"
|
- "5052:8080"
|
||||||
environment:
|
environment:
|
||||||
@@ -30,7 +21,6 @@ services:
|
|||||||
|
|
||||||
openscad-stl:
|
openscad-stl:
|
||||||
image: openscad
|
image: openscad
|
||||||
# build: ./openscad/.
|
|
||||||
command: openscad.stl
|
command: openscad.stl
|
||||||
ports:
|
ports:
|
||||||
- "5053:8080"
|
- "5053:8080"
|
||||||
@@ -42,7 +32,7 @@ services:
|
|||||||
cadquery-stl:
|
cadquery-stl:
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./cadquery/.
|
dockerfile: ./cadquery/Dockerfile
|
||||||
command: cadquery.stl
|
command: cadquery.stl
|
||||||
ports:
|
ports:
|
||||||
- 5060:8080
|
- 5060:8080
|
||||||
@@ -51,6 +41,3 @@ services:
|
|||||||
AWS_ACCESS_KEY_ID: "${DEV_AWS_ACCESS_KEY_ID}"
|
AWS_ACCESS_KEY_ID: "${DEV_AWS_ACCESS_KEY_ID}"
|
||||||
BUCKET: "${DEV_BUCKET}"
|
BUCKET: "${DEV_BUCKET}"
|
||||||
|
|
||||||
# networks:
|
|
||||||
# awsland:
|
|
||||||
# name: awsland
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createUserInsecure } from 'src/services/users/users.js'
|
import { createUserInsecure } from 'src/services/users/users'
|
||||||
import { db } from 'src/lib/db'
|
import { db } from 'src/lib/db'
|
||||||
import { sentryWrapper } from 'src/lib/sentry'
|
import { sentryWrapper } from 'src/lib/sentry'
|
||||||
import { enforceAlphaNumeric, generateUniqueString } from 'src/services/helpers'
|
import { enforceAlphaNumeric, generateUniqueString } from 'src/services/helpers'
|
||||||
import 'graphql-tag'
|
import 'graphql-tag'
|
||||||
|
import { sendMail } from 'src/lib/sendmail'
|
||||||
|
|
||||||
const unWrappedHandler = async (req, _context) => {
|
const unWrappedHandler = async (req, _context) => {
|
||||||
const body = JSON.parse(req.body)
|
const body = JSON.parse(req.body)
|
||||||
@@ -56,7 +57,7 @@ const unWrappedHandler = async (req, _context) => {
|
|||||||
const user = body.user
|
const user = body.user
|
||||||
const email = user.email
|
const email = user.email
|
||||||
|
|
||||||
let roles = []
|
const roles = []
|
||||||
|
|
||||||
if (eventType === 'signup') {
|
if (eventType === 'signup') {
|
||||||
roles.push('user')
|
roles.push('user')
|
||||||
@@ -73,6 +74,15 @@ const unWrappedHandler = async (req, _context) => {
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
}
|
}
|
||||||
await createUserInsecure({ input })
|
await createUserInsecure({ input })
|
||||||
|
await sendMail({
|
||||||
|
to: 'k.hutten@protonmail.ch',
|
||||||
|
from: {
|
||||||
|
address: 'news@mail.cadhub.xyz',
|
||||||
|
name: 'CadHub',
|
||||||
|
},
|
||||||
|
subject: `New Cadhub User`,
|
||||||
|
text: JSON.stringify(input, null, 2),
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
22
app/api/src/graphql/email.sdl.ts
Normal file
22
app/api/src/graphql/email.sdl.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const schema = gql`
|
||||||
|
type Envelope {
|
||||||
|
from: String
|
||||||
|
to: [String!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailResponse {
|
||||||
|
accepted: [String!]!
|
||||||
|
rejected: [String!]!
|
||||||
|
messageId: String!
|
||||||
|
envelope: Envelope
|
||||||
|
}
|
||||||
|
|
||||||
|
input Email {
|
||||||
|
subject: String!
|
||||||
|
body: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
sendAllUsersEmail(input: Email!): EmailResponse!
|
||||||
|
}
|
||||||
|
`
|
||||||
@@ -121,7 +121,8 @@ export const getCurrentUser = async (decoded, { _token, _type }) => {
|
|||||||
* requireAuth({ role: ['editor', 'author'] })
|
* requireAuth({ role: ['editor', 'author'] })
|
||||||
* requireAuth({ role: ['publisher'] })
|
* requireAuth({ role: ['publisher'] })
|
||||||
*/
|
*/
|
||||||
export const requireAuth = ({ role } = {}) => {
|
export const requireAuth = ({ role }: { role?: string | string[] } = {}) => {
|
||||||
|
console.log(context.currentUser)
|
||||||
if (!context.currentUser) {
|
if (!context.currentUser) {
|
||||||
throw new AuthenticationError("You don't have permission to do that.")
|
throw new AuthenticationError("You don't have permission to do that.")
|
||||||
}
|
}
|
||||||
63
app/api/src/lib/sendmail.ts
Normal file
63
app/api/src/lib/sendmail.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import nodemailer, { SendMailOptions } from 'nodemailer'
|
||||||
|
|
||||||
|
interface Args {
|
||||||
|
to: SendMailOptions['to']
|
||||||
|
from: SendMailOptions['from']
|
||||||
|
subject: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SuccessResult {
|
||||||
|
accepted: string[]
|
||||||
|
rejected: string[]
|
||||||
|
envelopeTime: number
|
||||||
|
messageTime: number
|
||||||
|
messageSize: number
|
||||||
|
response: string
|
||||||
|
envelope: {
|
||||||
|
from: string | false
|
||||||
|
to: string[]
|
||||||
|
}
|
||||||
|
messageId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendMail({
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
}: Args): Promise<SuccessResult> {
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: 'smtp.mailgun.org',
|
||||||
|
port: 587,
|
||||||
|
secure: false,
|
||||||
|
tls: {
|
||||||
|
ciphers: 'SSLv3',
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
user: 'postmaster@mail.cadhub.xyz',
|
||||||
|
pass: process.env.EMAIL_PASSWORD,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log({ to, from, subject, text })
|
||||||
|
|
||||||
|
const emailPromise = new Promise((resolve, reject) => {
|
||||||
|
transporter.sendMail(
|
||||||
|
{
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
(error, info) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}) as any as Promise<SuccessResult>
|
||||||
|
return emailPromise
|
||||||
|
}
|
||||||
26
app/api/src/services/email/email.ts
Normal file
26
app/api/src/services/email/email.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { requireAuth } from 'src/lib/auth'
|
||||||
|
import { sendMail } from 'src/lib/sendmail'
|
||||||
|
import { users } from 'src/services/users/users'
|
||||||
|
|
||||||
|
export const sendAllUsersEmail = async ({ input: { body, subject } }) => {
|
||||||
|
requireAuth({ role: 'admin' })
|
||||||
|
const recipients = (await users()).map(({ email }) => email)
|
||||||
|
const from = {
|
||||||
|
address: 'news@mail.cadhub.xyz',
|
||||||
|
name: 'CadHub',
|
||||||
|
}
|
||||||
|
const result = await sendMail({
|
||||||
|
to: recipients,
|
||||||
|
from,
|
||||||
|
subject,
|
||||||
|
text: body,
|
||||||
|
})
|
||||||
|
await sendMail({
|
||||||
|
to: 'k.hutten@protonmail.ch',
|
||||||
|
from,
|
||||||
|
subject: `All users email report`,
|
||||||
|
text: JSON.stringify(result, null, 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -16,7 +16,8 @@
|
|||||||
'SENTRY_DSN',
|
'SENTRY_DSN',
|
||||||
'SENTRY_AUTH_TOKEN',
|
'SENTRY_AUTH_TOKEN',
|
||||||
'SENTRY_ORG',
|
'SENTRY_ORG',
|
||||||
'SENTRY_PROJECT'
|
'SENTRY_PROJECT',
|
||||||
|
'EMAIL_PASSWORD'
|
||||||
]
|
]
|
||||||
# experimentalFastRefresh = true # this seems to break cascadeStudio
|
# experimentalFastRefresh = true # this seems to break cascadeStudio
|
||||||
[api]
|
[api]
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const Routes = () => {
|
|||||||
<Route path="/admin/subject-access-requests/{id}/edit" page={EditSubjectAccessRequestPage} name="editSubjectAccessRequest" />
|
<Route path="/admin/subject-access-requests/{id}/edit" page={EditSubjectAccessRequestPage} name="editSubjectAccessRequest" />
|
||||||
<Route path="/admin/subject-access-requests/{id}" page={SubjectAccessRequestPage} name="subjectAccessRequest" />
|
<Route path="/admin/subject-access-requests/{id}" page={SubjectAccessRequestPage} name="subjectAccessRequest" />
|
||||||
<Route path="/admin/subject-access-requests" page={SubjectAccessRequestsPage} name="subjectAccessRequests" />
|
<Route path="/admin/subject-access-requests" page={SubjectAccessRequestsPage} name="subjectAccessRequests" />
|
||||||
|
<Route path="/admin/email" page={AdminEmailPage} name="adminEmail" />
|
||||||
</Private>
|
</Private>
|
||||||
</Router>
|
</Router>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,18 +42,15 @@ export const makeStlDownloadHandler =
|
|||||||
thunkDispatch((dispatch, getState) => {
|
thunkDispatch((dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
if (state.ideType === 'openScad') {
|
if (state.ideType === 'openScad') {
|
||||||
thunkDispatch((dispatch, getState) => {
|
dispatch({ type: 'setLoading' })
|
||||||
const state = getState()
|
requestRender({
|
||||||
dispatch({ type: 'setLoading' })
|
state,
|
||||||
requestRender({
|
dispatch,
|
||||||
state,
|
code: state.code,
|
||||||
dispatch,
|
viewerSize: state.viewerSize,
|
||||||
code: state.code,
|
camera: state.camera,
|
||||||
viewerSize: state.viewerSize,
|
specialCadProcess: 'stl',
|
||||||
camera: state.camera,
|
}).then((result) => result && saveFile(result.data))
|
||||||
specialCadProcess: 'stl',
|
|
||||||
}).then((result) => result && saveFile(result.data))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ const Footer = () => {
|
|||||||
>
|
>
|
||||||
Road Map
|
Road Map
|
||||||
</OutBound>
|
</OutBound>
|
||||||
<OutBound
|
<OutBound className="mr-8" to="https://learn.cadhub.xyz/blog">
|
||||||
className="mr-8"
|
|
||||||
to="https://learn.cadhub.xyz/blog"
|
|
||||||
>
|
|
||||||
Blog
|
Blog
|
||||||
</OutBound>
|
</OutBound>
|
||||||
<Link className="mr-8" to={routes.codeOfConduct()}>
|
<Link className="mr-8" to={routes.codeOfConduct()}>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { requestRender } from 'src/helpers/hooks/useIdeState'
|
|||||||
import texture from './dullFrontLitMetal.png'
|
import texture from './dullFrontLitMetal.png'
|
||||||
import { TextureLoader } from 'three/src/loaders/TextureLoader'
|
import { TextureLoader } from 'three/src/loaders/TextureLoader'
|
||||||
|
|
||||||
const loader = new TextureLoader
|
const loader = new TextureLoader()
|
||||||
const colorMap = loader.load(texture)
|
const colorMap = loader.load(texture)
|
||||||
|
|
||||||
extend({ OrbitControls })
|
extend({ OrbitControls })
|
||||||
@@ -200,8 +200,8 @@ const IdeViewer = ({ Loading }) => {
|
|||||||
/>
|
/>
|
||||||
<ambientLight intensity={1} />
|
<ambientLight intensity={1} />
|
||||||
<pointLight position={[15, 5, 10]} intensity={4} />
|
<pointLight position={[15, 5, 10]} intensity={4} />
|
||||||
<pointLight position={[-1000, -1000, -1000]} intensity={1}/>
|
<pointLight position={[-1000, -1000, -1000]} intensity={1} />
|
||||||
<pointLight position={[-1000, 0, 1000]} intensity={1}/>
|
<pointLight position={[-1000, 0, 1000]} intensity={1} />
|
||||||
{state.objectData?.type === 'png' && (
|
{state.objectData?.type === 'png' && (
|
||||||
<>
|
<>
|
||||||
<Sphere position={[0, 0, 0]} color={pink400} />
|
<Sphere position={[0, 0, 0]} color={pink400} />
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
export const render = async ({ code }) => {
|
export const render = async ({ code }) => {
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
settings: {
|
settings: {
|
||||||
deflection: 0.2
|
deflection: 0.2,
|
||||||
},
|
},
|
||||||
file: code,
|
file: code,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
|||||||
|
|
||||||
export const lambdaBaseURL =
|
export const lambdaBaseURL =
|
||||||
process.env.CAD_LAMBDA_BASE_URL ||
|
process.env.CAD_LAMBDA_BASE_URL ||
|
||||||
'https://2inlbple1b.execute-api.us-east-1.amazonaws.com/prod2'
|
'https://oxt2p7ddgj.execute-api.us-east-1.amazonaws.com/prod'
|
||||||
|
|
||||||
export const stlToGeometry = (url) =>
|
export const stlToGeometry = (url) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
|
|||||||
59
app/web/src/pages/AdminEmailPage/AdminEmailPage.tsx
Normal file
59
app/web/src/pages/AdminEmailPage/AdminEmailPage.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useMutation } from '@redwoodjs/web'
|
||||||
|
import { toast, Toaster } from '@redwoodjs/web/toast'
|
||||||
|
|
||||||
|
const SEND_EMAIL_MUTATION = gql`
|
||||||
|
mutation sendEmailMutation($email: Email!) {
|
||||||
|
sendAllUsersEmail(input: $email) {
|
||||||
|
accepted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const AdminEmailPage = () => {
|
||||||
|
const [subject, setSubject] = useState('')
|
||||||
|
const [body, setBody] = useState('')
|
||||||
|
const [sendEmailMutation] = useMutation(SEND_EMAIL_MUTATION, {
|
||||||
|
onCompleted: ({ sendAllUsersEmail }) => {
|
||||||
|
toast.success(`Emails sent, ${sendAllUsersEmail?.accepted.join(', ')}`)
|
||||||
|
setSubject('')
|
||||||
|
setBody('')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const sendEmail = () =>
|
||||||
|
sendEmailMutation({ variables: { email: { subject, body } } })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div className="max-w-7xl pt-8">
|
||||||
|
<h2 className="" style={{ width: '46rem' }}>
|
||||||
|
Email all users
|
||||||
|
</h2>
|
||||||
|
<label htmlFor="subject">Subject</label>
|
||||||
|
<input
|
||||||
|
name="subject"
|
||||||
|
className="rounded border border-gray-400 px-2 w-full"
|
||||||
|
value={subject}
|
||||||
|
onChange={({ target }) => setSubject(target.value)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="body">Body</label>
|
||||||
|
<textarea
|
||||||
|
className="w-full rounded border border-gray-400 p-2"
|
||||||
|
name="text"
|
||||||
|
value={body}
|
||||||
|
onChange={({ target }) => setBody(target.value)}
|
||||||
|
></textarea>
|
||||||
|
<button
|
||||||
|
className="rounded px-2 p-1 mt-4 bg-ch-purple-400 text-indigo-200"
|
||||||
|
onClick={sendEmail}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<Toaster timeout={1500} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminEmailPage
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import SubjectAccessRequestsCell from 'src/components/SubjectAccessRequestsCell'
|
import SubjectAccessRequestsCell from 'src/components/SubjectAccessRequestsCell'
|
||||||
import { Flash, useQuery, useMutation, useFlash } from '@redwoodjs/web'
|
import { useQuery, useMutation } from '@redwoodjs/web'
|
||||||
import { Form, Submit } from '@redwoodjs/forms'
|
import { Form, Submit } from '@redwoodjs/forms'
|
||||||
|
|
||||||
import MainLayout from 'src/layouts/MainLayout'
|
import MainLayout from 'src/layouts/MainLayout'
|
||||||
|
|||||||
@@ -3963,6 +3963,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
|
||||||
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
|
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
|
||||||
|
|
||||||
|
"@types/nodemailer@^6.4.2":
|
||||||
|
version "6.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.2.tgz#d8ee254c969e6ad83fb9a0a0df3a817406a3fa3b"
|
||||||
|
integrity sha512-yhsqg5Xbr8aWdwjFS3QjkniW5/tLpWXtOYQcJdo9qE3DolBxsKzgRCQrteaMY0hos8MklJNSEsMqDpZynGzMNg==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||||
@@ -13070,6 +13077,11 @@ node-releases@^1.1.29, node-releases@^1.1.61, node-releases@^1.1.71:
|
|||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20"
|
||||||
integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==
|
integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==
|
||||||
|
|
||||||
|
nodemailer@^6.6.2:
|
||||||
|
version "6.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.6.2.tgz#e184c9ed5bee245a3e0bcabc7255866385757114"
|
||||||
|
integrity sha512-YSzu7TLbI+bsjCis/TZlAXBoM4y93HhlIgo0P5oiA2ua9Z4k+E2Fod//ybIzdJxOlXGRcHIh/WaeCBehvxZb/Q==
|
||||||
|
|
||||||
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
|
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||||
|
|||||||
Reference in New Issue
Block a user