mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-09 19:16:02 +08:00
Revise landing messaging and add news feed
This commit is contained in:
60
server/api/service/roadmap-suggestions.post.ts
Normal file
60
server/api/service/roadmap-suggestions.post.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { readBody, createError } from 'h3'
|
||||
import { RoadmapSuggestion } from '../../models/RoadmapSuggestion'
|
||||
import { sendAdminNotification } from '../../utils/notifications'
|
||||
|
||||
interface RoadmapSuggestionBody {
|
||||
title?: string
|
||||
details?: string
|
||||
email?: string
|
||||
allowContact?: boolean
|
||||
consentPrivacy?: boolean
|
||||
}
|
||||
|
||||
const MIN_TITLE_LENGTH = 4
|
||||
const MIN_DETAILS_LENGTH = 20
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody<RoadmapSuggestionBody>(event)
|
||||
const title = body.title?.trim()
|
||||
const details = body.details?.trim()
|
||||
const email = body.email?.trim().toLowerCase()
|
||||
const allowContact = Boolean(body.allowContact && email)
|
||||
|
||||
if (!title || title.length < MIN_TITLE_LENGTH) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Bitte gib einen kurzen Titel (mindestens 4 Zeichen) an.' })
|
||||
}
|
||||
|
||||
if (!details || details.length < MIN_DETAILS_LENGTH) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Beschreibe deinen Vorschlag mit mindestens 20 Zeichen.' })
|
||||
}
|
||||
|
||||
if (!body.consentPrivacy) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Wir benötigen deine Einwilligung zur Datenschutzerklärung.' })
|
||||
}
|
||||
|
||||
if (body.allowContact && !email) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Bitte gib eine E-Mail an, damit wir dich kontaktieren können.' })
|
||||
}
|
||||
|
||||
const suggestion = await RoadmapSuggestion.create({
|
||||
title,
|
||||
details,
|
||||
email,
|
||||
allowContact,
|
||||
consentPrivacy: true,
|
||||
})
|
||||
|
||||
const lines = [
|
||||
`Titel: ${title}`,
|
||||
`Beschreibung: ${details}`,
|
||||
`Kontakt: ${email || '—'}`,
|
||||
allowContact ? 'Kontaktaufnahme erwünscht' : 'Keine Kontaktaufnahme erwünscht',
|
||||
]
|
||||
|
||||
await sendAdminNotification('[OpenSquawk] Neuer Roadmap-Vorschlag', lines.join('\n'))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
suggestionId: suggestion._id.toString(),
|
||||
}
|
||||
})
|
||||
52
server/api/service/updates.post.ts
Normal file
52
server/api/service/updates.post.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { readBody, createError } from 'h3'
|
||||
import { registerUpdateSubscriber } from '../../utils/subscribers'
|
||||
import { sendAdminNotification } from '../../utils/notifications'
|
||||
|
||||
interface UpdatesRequestBody {
|
||||
email?: string
|
||||
name?: string
|
||||
consentPrivacy?: boolean
|
||||
consentMarketing?: boolean
|
||||
source?: string
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody<UpdatesRequestBody>(event)
|
||||
const email = body.email?.trim().toLowerCase()
|
||||
const name = body.name?.trim()
|
||||
const source = body.source?.trim() || 'landing-updates'
|
||||
|
||||
if (!email) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'E-Mail wird benötigt' })
|
||||
}
|
||||
|
||||
if (!body.consentPrivacy) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Bitte bestätige die Datenschutzerklärung' })
|
||||
}
|
||||
|
||||
if (!body.consentMarketing) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Wir benötigen deine Einwilligung für Produkt-Updates per E-Mail' })
|
||||
}
|
||||
|
||||
const result = await registerUpdateSubscriber({
|
||||
email,
|
||||
name,
|
||||
source,
|
||||
consentPrivacy: true,
|
||||
consentMarketing: true,
|
||||
})
|
||||
|
||||
if (result.created) {
|
||||
const lines = [
|
||||
`E-Mail: ${email}`,
|
||||
name ? `Name: ${name}` : null,
|
||||
`Quelle: ${source}`,
|
||||
].filter(Boolean)
|
||||
await sendAdminNotification('[OpenSquawk] Neue Updates-Liste', lines.join('\n'))
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
alreadySubscribed: !result.created,
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
import { readBody, createError } from 'h3'
|
||||
import { WaitlistEntry } from '../../models/WaitlistEntry'
|
||||
import { sendAdminNotification } from '../../utils/notifications'
|
||||
import { registerUpdateSubscriber } from '../../utils/subscribers'
|
||||
|
||||
interface WaitlistRequestBody {
|
||||
email?: string
|
||||
@@ -8,6 +10,7 @@ interface WaitlistRequestBody {
|
||||
consentPrivacy?: boolean
|
||||
consentTerms?: boolean
|
||||
source?: string
|
||||
wantsProductUpdates?: boolean
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
@@ -16,6 +19,7 @@ export default defineEventHandler(async (event) => {
|
||||
const name = body.name?.trim()
|
||||
const notes = body.notes?.trim()
|
||||
const source = body.source?.trim() || 'landing'
|
||||
const wantsProductUpdates = Boolean(body.wantsProductUpdates)
|
||||
|
||||
if (!email) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'E-Mail wird benötigt' })
|
||||
@@ -30,12 +34,41 @@ export default defineEventHandler(async (event) => {
|
||||
const existing = await WaitlistEntry.findOne({ email })
|
||||
|
||||
if (existing) {
|
||||
const previouslyWantedUpdates = Boolean(existing.wantsProductUpdates)
|
||||
existing.name = name || existing.name
|
||||
existing.notes = notes || existing.notes
|
||||
existing.source = source
|
||||
existing.consentPrivacy = true
|
||||
existing.consentTerms = true
|
||||
if (wantsProductUpdates && !previouslyWantedUpdates) {
|
||||
existing.wantsProductUpdates = true
|
||||
existing.updatesOptedInAt = now
|
||||
}
|
||||
await existing.save()
|
||||
|
||||
if (wantsProductUpdates) {
|
||||
const updateResult = await registerUpdateSubscriber({
|
||||
email,
|
||||
name,
|
||||
source: `${source}-waitlist`,
|
||||
consentPrivacy: true,
|
||||
consentMarketing: true,
|
||||
})
|
||||
|
||||
if (!previouslyWantedUpdates && updateResult.created) {
|
||||
const lines = [
|
||||
`E-Mail: ${email}`,
|
||||
name ? `Name: ${name}` : null,
|
||||
notes ? `Notizen: ${notes}` : null,
|
||||
`Quelle: ${source}`,
|
||||
].filter(Boolean)
|
||||
await sendAdminNotification(
|
||||
'[OpenSquawk] Neue Updates-Liste (via Warteliste)',
|
||||
`${lines.join('\n')}\nOpt-In: Produkt-Updates`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
alreadyJoined: true,
|
||||
@@ -51,8 +84,29 @@ export default defineEventHandler(async (event) => {
|
||||
consentPrivacy: true,
|
||||
consentTerms: true,
|
||||
joinedAt: now,
|
||||
wantsProductUpdates,
|
||||
updatesOptedInAt: wantsProductUpdates ? now : undefined,
|
||||
})
|
||||
|
||||
if (wantsProductUpdates) {
|
||||
await registerUpdateSubscriber({
|
||||
email,
|
||||
name,
|
||||
source: `${source}-waitlist`,
|
||||
consentPrivacy: true,
|
||||
consentMarketing: true,
|
||||
})
|
||||
}
|
||||
|
||||
const lines = [
|
||||
`E-Mail: ${email}`,
|
||||
name ? `Name: ${name}` : null,
|
||||
notes ? `Notizen: ${notes}` : null,
|
||||
`Quelle: ${source}`,
|
||||
wantsProductUpdates ? 'Opt-In: Produkt-Updates' : 'Opt-In: nur Warteliste',
|
||||
].filter(Boolean)
|
||||
await sendAdminNotification('[OpenSquawk] Neue Wartelisten-Anmeldung', lines.join('\n'))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
alreadyJoined: false,
|
||||
|
||||
Reference in New Issue
Block a user