mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 06:06:42 +02:00
feat: integrate uload and picture, unify package naming
- 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>
This commit is contained in:
parent
c6c4c5a552
commit
c712a2504a
1031 changed files with 189301 additions and 290 deletions
292
uload/backend/pb_hooks/team_invitations.pb.js.disabled
Normal file
292
uload/backend/pb_hooks/team_invitations.pb.js.disabled
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/// <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");
|
||||
Loading…
Add table
Add a link
Reference in a new issue