mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 11:21:09 +02:00
- Add uload project with apps/web structure
- Reorganize from flat to monorepo structure
- Remove PocketBase binary and local data
- Update to pnpm and @uload/web namespace
- Add picture project to monorepo
- Remove embedded git repository
- Unify all package names to @{project}/{app} schema:
- @maerchenzauber/* (was @storyteller/*)
- @manacore/* (was manacore-*, manacore)
- @manadeck/* (was web, backend, manadeck)
- @memoro/* (was memoro-web, landing, memoro)
- @picture/* (already unified)
- @uload/web
- Add convenient dev scripts for all apps:
- pnpm dev:{project}:web
- pnpm dev:{project}:landing
- pnpm dev:{project}:mobile
- pnpm dev:{project}:backend
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
292 lines
No EOL
14 KiB
Text
292 lines
No EOL
14 KiB
Text
/// <reference path="../pb_data/types.d.ts" />
|
|
|
|
// Hook for sending team invitation emails
|
|
onRecordAfterCreateRequest((e) => {
|
|
// Handle new team invitations for existing users
|
|
if (e.collection.name === "workspace_members") {
|
|
const record = e.record;
|
|
|
|
// Only send email for pending invitations
|
|
if (record.get("invitation_status") !== "pending") {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Get the invited user's email and workspace owner
|
|
const user = $app.dao().findRecordById("users", record.get("user"));
|
|
const workspace = $app.dao().findRecordById("workspaces", record.get("workspace"));
|
|
const owner = workspace ? $app.dao().findRecordById("users", workspace.get("owner")) : null;
|
|
|
|
if (!user || !owner) {
|
|
console.log("Could not find user or owner");
|
|
return;
|
|
}
|
|
|
|
const inviterName = owner.get("name") || owner.get("username") || owner.get("email");
|
|
const token = record.get("invitation_token");
|
|
const appUrl = "https://ulo.ad"; // Change this to your domain
|
|
|
|
// Email content
|
|
const subject = `${inviterName} hat dich zu seinem Team eingeladen / invited you to their team - ulo.ad 👥`;
|
|
|
|
const html = `
|
|
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f8fafc;">
|
|
<div style="text-align: center; margin-bottom: 30px; padding: 20px;">
|
|
<h1 style="color: #0ea5e9; font-size: 36px; margin: 0; font-weight: 700;">🔗 ulo.ad</h1>
|
|
</div>
|
|
|
|
<div style="background: #ffffff; border-radius: 16px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
|
<h2 style="color: #0f172a; font-size: 24px; margin-top: 0; margin-bottom: 16px; font-weight: 600;">
|
|
👥 Team-Einladung / Team Invitation
|
|
</h2>
|
|
|
|
<p style="color: #475569; font-size: 16px; line-height: 1.6; margin-bottom: 24px;">
|
|
<strong>🇩🇪</strong> ${inviterName} hat dich eingeladen, Teil des Teams zu werden!<br>
|
|
<strong>🇬🇧</strong> ${inviterName} has invited you to join their team!
|
|
</p>
|
|
|
|
<div style="background: #f0f9ff; border-radius: 12px; padding: 16px; margin: 24px 0; border: 1px solid #bae6fd;">
|
|
<p style="color: #0369a1; font-size: 14px; margin: 0;">
|
|
<strong>Von / From:</strong> ${owner.get("email")}<br>
|
|
<strong>An / To:</strong> ${user.get("email")}
|
|
</p>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin: 32px 0;">
|
|
<a href="${appUrl}/team/accept-invite?token=${token}"
|
|
style="display: inline-block; background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);
|
|
color: white; padding: 16px 40px; border-radius: 10px;
|
|
text-decoration: none; font-weight: 600; font-size: 16px;">
|
|
✅ Einladung annehmen / Accept Invitation
|
|
</a>
|
|
</div>
|
|
|
|
<div style="background: #fef2f2; border: 1px solid #fecaca; border-radius: 12px; padding: 12px; margin: 24px 0; text-align: center;">
|
|
<p style="color: #991b1b; font-size: 13px; margin: 0;">
|
|
⏱️ Diese Einladung ist 7 Tage gültig / This invitation is valid for 7 days
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
// Send email using PocketBase's mailer
|
|
const message = new MailerMessage({
|
|
from: {
|
|
address: $app.settings().meta.senderAddress,
|
|
name: $app.settings().meta.senderName,
|
|
},
|
|
to: [{address: user.get("email"), name: user.get("name") || user.get("username")}],
|
|
subject: subject,
|
|
html: html,
|
|
});
|
|
|
|
$app.newMailClient().send(message);
|
|
console.log(`Team invitation email sent to ${user.get("email")}`);
|
|
|
|
// Create in-app notification for the invited user
|
|
try {
|
|
const notification = new Record($app.dao().findCollectionByNameOrId("notifications"));
|
|
notification.set("user", user.id);
|
|
notification.set("type", "team_invite");
|
|
notification.set("title", "Neue Team-Einladung / New Team Invitation");
|
|
notification.set("message", `${inviterName} hat dich zu seinem Team eingeladen / invited you to their team`);
|
|
notification.set("data", {
|
|
invitation_id: record.id,
|
|
owner_id: owner.id,
|
|
owner_name: inviterName,
|
|
invitation_token: token
|
|
});
|
|
notification.set("read", false);
|
|
notification.set("action_url", `${appUrl}/team/accept-invite?token=${token}`);
|
|
|
|
$app.dao().saveRecord(notification);
|
|
console.log(`In-app notification created for user ${user.get("email")}`);
|
|
} catch (notifError) {
|
|
console.error("Failed to create in-app notification:", notifError);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Failed to send team invitation email:", error);
|
|
}
|
|
}
|
|
}, "workspace_members");
|
|
|
|
// Hook for sending invitations to new users (not registered yet)
|
|
onRecordAfterCreateRequest((e) => {
|
|
if (e.collection.name === "pending_invitations") {
|
|
const record = e.record;
|
|
|
|
try {
|
|
const owner = $app.dao().findRecordById("users", record.get("owner"));
|
|
|
|
if (!owner) {
|
|
console.log("Could not find invitation owner");
|
|
return;
|
|
}
|
|
|
|
const inviterName = owner.get("name") || owner.get("username") || owner.get("email");
|
|
const token = record.get("token");
|
|
const recipientEmail = record.get("email");
|
|
const appUrl = "https://ulo.ad"; // Change this to your domain
|
|
|
|
// Email content for new users
|
|
const subject = `${inviterName} hat dich zu ulo.ad eingeladen / invited you to ulo.ad - 🚀`;
|
|
|
|
const html = `
|
|
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f8fafc;">
|
|
<div style="text-align: center; margin-bottom: 30px; padding: 20px;">
|
|
<h1 style="color: #0ea5e9; font-size: 36px; margin: 0; font-weight: 700;">🔗 ulo.ad</h1>
|
|
</div>
|
|
|
|
<div style="background: #ffffff; border-radius: 16px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
|
<h2 style="color: #0f172a; font-size: 24px; margin-top: 0; margin-bottom: 16px; font-weight: 600;">
|
|
🎉 Willkommen bei ulo.ad / Welcome to ulo.ad
|
|
</h2>
|
|
|
|
<p style="color: #475569; font-size: 16px; line-height: 1.6; margin-bottom: 24px;">
|
|
<strong>🇩🇪</strong> ${inviterName} hat dich eingeladen, Teil des Teams zu werden!<br>
|
|
<strong>🇬🇧</strong> ${inviterName} has invited you to join their team!
|
|
</p>
|
|
|
|
<div style="background: #dcfce7; border: 1px solid #86efac; border-radius: 12px; padding: 16px; margin: 24px 0;">
|
|
<p style="color: #14532d; font-size: 14px; margin: 0 0 12px 0; font-weight: 600;">
|
|
🆕 Neu bei ulo.ad? / New to ulo.ad?
|
|
</p>
|
|
<p style="color: #166534; font-size: 13px; margin: 0;">
|
|
<strong>🇩🇪</strong> Kein Problem! Erstelle einfach einen kostenlosen Account und die Einladung wird automatisch angenommen.<br>
|
|
<strong>🇬🇧</strong> No problem! Simply create a free account and the invitation will be accepted automatically.
|
|
</p>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin: 32px 0;">
|
|
<a href="${appUrl}/register?invite=${token}"
|
|
style="display: inline-block; background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
color: white; padding: 16px 40px; border-radius: 10px;
|
|
text-decoration: none; font-weight: 600; font-size: 16px;">
|
|
🚀 Account erstellen & Team beitreten / Create Account & Join Team
|
|
</a>
|
|
</div>
|
|
|
|
<div style="background: #fef2f2; border: 1px solid #fecaca; border-radius: 12px; padding: 12px; margin: 24px 0; text-align: center;">
|
|
<p style="color: #991b1b; font-size: 13px; margin: 0;">
|
|
⏱️ Diese Einladung ist 7 Tage gültig / This invitation is valid for 7 days
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
// Send email
|
|
const message = new MailerMessage({
|
|
from: {
|
|
address: $app.settings().meta.senderAddress,
|
|
name: $app.settings().meta.senderName,
|
|
},
|
|
to: [{address: recipientEmail}],
|
|
subject: subject,
|
|
html: html,
|
|
});
|
|
|
|
$app.newMailClient().send(message);
|
|
console.log(`New user invitation email sent to ${recipientEmail}`);
|
|
|
|
} catch (error) {
|
|
console.error("Failed to send new user invitation email:", error);
|
|
}
|
|
}
|
|
}, "pending_invitations");
|
|
|
|
// Hook for sending acceptance notifications
|
|
onRecordAfterUpdateRequest((e) => {
|
|
if (e.collection.name === "workspace_members") {
|
|
const record = e.record;
|
|
const originalRecord = e.originalRecord;
|
|
|
|
// Check if invitation was just accepted
|
|
if (originalRecord.get("invitation_status") === "pending" &&
|
|
record.get("invitation_status") === "accepted") {
|
|
|
|
try {
|
|
const user = $app.dao().findRecordById("users", record.get("user"));
|
|
const workspace = $app.dao().findRecordById("workspaces", record.get("workspace"));
|
|
const owner = workspace ? $app.dao().findRecordById("users", workspace.get("owner")) : null;
|
|
|
|
if (!user || !owner) {
|
|
return;
|
|
}
|
|
|
|
const subject = `${user.get("email")} hat deine Einladung angenommen / accepted your invitation - ulo.ad ✅`;
|
|
|
|
const html = `
|
|
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f8fafc;">
|
|
<div style="text-align: center; margin-bottom: 30px; padding: 20px;">
|
|
<h1 style="color: #0ea5e9; font-size: 36px; margin: 0; font-weight: 700;">🔗 ulo.ad</h1>
|
|
</div>
|
|
|
|
<div style="background: #ffffff; border-radius: 16px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
|
<div style="text-align: center; margin-bottom: 24px;">
|
|
<div style="display: inline-block; background: #dcfce7; border-radius: 50%; padding: 16px;">
|
|
<span style="font-size: 48px;">✅</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 style="color: #0f172a; font-size: 24px; margin-top: 0; margin-bottom: 16px; font-weight: 600; text-align: center;">
|
|
Einladung angenommen / Invitation Accepted
|
|
</h2>
|
|
|
|
<p style="color: #475569; font-size: 16px; line-height: 1.6; margin-bottom: 24px; text-align: center;">
|
|
<strong>🇩🇪</strong> ${user.get("email")} hat deine Team-Einladung angenommen!<br>
|
|
<strong>🇬🇧</strong> ${user.get("email")} has accepted your team invitation!
|
|
</p>
|
|
|
|
<div style="text-align: center; margin: 32px 0;">
|
|
<a href="https://ulo.ad/settings/team"
|
|
style="display: inline-block; background: #0ea5e9;
|
|
color: white; padding: 12px 24px; border-radius: 8px;
|
|
text-decoration: none; font-weight: 600; font-size: 14px;">
|
|
👥 Team verwalten / Manage Team
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
// Send notification to owner
|
|
const message = new MailerMessage({
|
|
from: {
|
|
address: $app.settings().meta.senderAddress,
|
|
name: $app.settings().meta.senderName,
|
|
},
|
|
to: [{address: owner.get("email"), name: owner.get("name") || owner.get("username")}],
|
|
subject: subject,
|
|
html: html,
|
|
});
|
|
|
|
$app.newMailClient().send(message);
|
|
console.log(`Acceptance notification sent to ${owner.get("email")}`);
|
|
|
|
// Create in-app notification for the owner
|
|
try {
|
|
const notification = new Record($app.dao().findCollectionByNameOrId("notifications"));
|
|
notification.set("user", owner.id);
|
|
notification.set("type", "team_accepted");
|
|
notification.set("title", "Team-Einladung angenommen / Team Invitation Accepted");
|
|
notification.set("message", `${user.get("email")} ist deinem Team beigetreten / has joined your team`);
|
|
notification.set("data", {
|
|
member_id: user.id,
|
|
member_email: user.get("email"),
|
|
member_name: user.get("name") || user.get("username") || user.get("email")
|
|
});
|
|
notification.set("read", false);
|
|
notification.set("action_url", "https://ulo.ad/settings/team");
|
|
|
|
$app.dao().saveRecord(notification);
|
|
console.log(`In-app notification created for owner ${owner.get("email")}`);
|
|
} catch (notifError) {
|
|
console.error("Failed to create owner notification:", notifError);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Failed to send acceptance notification:", error);
|
|
}
|
|
}
|
|
}
|
|
}, "workspace_members"); |