Skip to content

Commit da8ec0b

Browse files
committed
feat: add invitation email option
1 parent a3bbbe9 commit da8ec0b

7 files changed

Lines changed: 151 additions & 13 deletions

File tree

client/src/pages/TripInvitation.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function TripInvitation({
3535
email: "",
3636
message: "",
3737
});
38-
38+
const [copyLink, setCopyLink] = useState(false);
3939
const [loading, setLoading] = useState(false);
4040

4141
const formatDate = (dateString: string) => {
@@ -62,6 +62,7 @@ function TripInvitation({
6262

6363
const cancelInvitation = (e: React.MouseEvent<HTMLButtonElement>) => {
6464
setInvitationForm({ email: "", message: "" });
65+
setCopyLink(false);
6566
if (onClose) onClose(e);
6667
};
6768

@@ -74,7 +75,7 @@ function TripInvitation({
7475
const copyToClipboard = async (text: string) => {
7576
try {
7677
await navigator.clipboard.writeText(text);
77-
toast.success("Lien dinvitation copié 📋");
78+
toast.success("Lien d'invitation copié");
7879
} catch {
7980
toast.error("Impossible de copier le lien");
8081
}
@@ -100,8 +101,13 @@ function TripInvitation({
100101
throw new Error(data.error || "Erreur lors de l'envoi");
101102
}
102103

103-
await copyToClipboard(data.invitationLink);
104+
if (copyLink) {
105+
await copyToClipboard(data.invitationLink);
106+
toast.success("Email envoyé avec succès");
107+
}
108+
104109
setInvitationForm({ email: "", message: "" });
110+
setCopyLink(false);
105111
} catch {
106112
toast.error("Erreur lors de l'envoi de l'invitation");
107113
} finally {
@@ -120,7 +126,6 @@ function TripInvitation({
120126
}}
121127
aria-modal="true"
122128
>
123-
124129
<ToastContainer position="top-right" autoClose={3000} theme="light" />
125130
<article className="tripinvitation-invitation-form">
126131
<div className="tripinvitation-head">
@@ -162,6 +167,7 @@ function TripInvitation({
162167
onChange={updateInvitationForm}
163168
required
164169
placeholder="adresse@email.com"
170+
disabled={loading}
165171
/>
166172
</label>
167173

@@ -172,15 +178,28 @@ function TripInvitation({
172178
value={invitationForm.message}
173179
onChange={updateInvitationForm}
174180
placeholder="Ajoutez un message personnalisé..."
181+
disabled={loading}
175182
/>
176183
</label>
177184

185+
<label>
186+
<div className="tripinvitation-copy-checkbox">
187+
<input
188+
type="checkbox"
189+
checked={copyLink}
190+
onChange={(e) => setCopyLink(e.target.checked)}
191+
disabled={loading}
192+
/>
193+
Copier aussi le lien d'invitation
194+
</div>
195+
</label>
196+
178197
<button
179198
type="submit"
180199
className="tripinvitation-btn-send-invitation"
181-
disabled={loading}
200+
disabled={loading || !invitationForm.email}
182201
>
183-
{loading ? "Copie..." : "Copier le lien d'invitation"}
202+
{loading ? "Envoi..." : "Envoyer par email"}
184203
</button>
185204

186205
<button

client/src/pages/styles/TripInvitation.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,19 @@
143143
.tripinvitation-btn-cancel-invitation:hover {
144144
background: #f3f4f6;
145145
}
146+
.tripinvitation-copy-checkbox {
147+
display: flex;
148+
align-items: flex-start;
149+
gap: 0.5rem;
150+
font-size: 0.9rem;
151+
color: #666;
152+
margin-bottom: 1rem;
153+
}
154+
155+
.tripinvitation-copy-checkbox input[type="checkbox"] {
156+
margin-top: 0.05rem;
157+
flex-shrink: 0;
158+
}
146159

147160
@media (max-width: 768px) {
148161
.tripinvitation-invitation-form {

package-lock.json

Lines changed: 78 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/.env.sample

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ CLIENT_URL=http://localhost:3000
1616

1717
GOOGLE_API_KEY=YOUR_API_KEY
1818

19+
RESEND_API_KEY=YOUR_API_KEY
20+
RESEND_FROM="YOUR NAME <NAME@DOMAIN.com>"
21+
22+
1923
# About specific needs, please ask your trainer about the deploiement project name and follow the pattern
2024
# You can add as much variable as needed. Don't forget to tell your trainer about it. Otherwise, it could break on deploiement
2125
PROJECT_NAME_SPECIFIC_NAME=YOUR_SPECIFIC_VALUE

server/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"joi": "^18.0.2",
1919
"jsonwebtoken": "^9.0.3",
2020
"mysql2": "^3.16.0",
21-
"react-toastify": "^11.0.5"
21+
"react-toastify": "^11.0.5",
22+
"resend": "^6.9.4"
2223
},
2324
"devDependencies": {
2425
"@faker-js/faker": "^9.6.0",

server/src/libs/resend.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Resend } from "resend";
2+
3+
const resend = new Resend(process.env.RESEND_API_KEY);
4+
5+
export default resend;

server/src/modules/invitation/invitationActions.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { RequestHandler } from "express";
22
import tripRepository from "../trip/tripRepository";
33
import userRepository from "../user/userRepository";
44
import invitationRepository from "./invitationRepository";
5+
import resend from "../../libs/resend";
56

67
const read: RequestHandler = async (req, res, next) => {
78
try {
@@ -110,7 +111,29 @@ const add: RequestHandler = async (req, res, next) => {
110111

111112
const invitationLink = `${clientUrl}/trip/${tripId}/invitation/${invitationId}`;
112113

113-
res.status(201).json({ invitationLink });
114+
const { data, error } = await resend.emails.send({
115+
from: process.env.RESEND_FROM as string,
116+
to: email,
117+
subject: "Invitation à rejoindre un voyage sur TripTogether",
118+
html: `
119+
<p>Bonjour,</p>
120+
<p>Vous avez reçu une invitation à rejoindre un voyage sur TripTogether.</p>
121+
<p>Message :</p>
122+
<blockquote>${message}</blockquote>
123+
<p>Pour voir l'invitation et répondre, cliquez sur le lien ci-dessous :</p>
124+
<p><a href="${invitationLink}">${invitationLink}</a></p>
125+
<p>À bientôt sur TripTogether !</p>
126+
`,
127+
});
128+
129+
if (error) {
130+
// à toi de voir : soit tu lances une erreur, soit tu continues mais tu logs
131+
console.error("Erreur envoi email invitation:", error);
132+
// Option 1 : throw error pour que le front sache que l’email n’est pas parti
133+
// throw new Error("Erreur lors de l'envoi de l'email d'invitation");
134+
}
135+
136+
res.status(201).json({ invitationLink, emailSent: !error, emailData: data });
114137
} catch (err) {
115138
next(err);
116139
}

0 commit comments

Comments
 (0)