Skip to content

Commit aa4d9a5

Browse files
modified accept invite
1 parent 3c76b7d commit aa4d9a5

File tree

4 files changed

+186
-37
lines changed

4 files changed

+186
-37
lines changed

Codespace_Service/src/controllers/codespaceController.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class CodespaceController {
1515
}
1616
}
1717

18-
static async getallinvitedusers(req, res, next) {
18+
static async getallinvitedusers(req, res, next) {
1919
try {
2020
const { id } = req.params;
2121
const invitedUsers = await CodespaceService.getAllInvitedUsers(id);
@@ -28,8 +28,7 @@ static async getallinvitedusers(req, res, next) {
2828
}
2929
}
3030

31-
32-
static async removeMember(req, res, next) {
31+
static async removeMember(req, res, next) {
3332
try {
3433
const { codespaceId, email } = req.params;
3534
await CodespaceService.removeMember(codespaceId, email);
@@ -39,7 +38,7 @@ static async removeMember(req, res, next) {
3938
});
4039
} catch (error) {
4140
if (error.statusCode) {
42-
return res.status(error.statusCode).json({
41+
return res.status(error.statusCode).json({
4342
error: error.message,
4443
code: error.code,
4544
});
@@ -48,7 +47,6 @@ static async removeMember(req, res, next) {
4847
}
4948
}
5049

51-
5250
static async createCodespace(req, res, next) {
5351
try {
5452
const { name } = req.body;
@@ -219,6 +217,26 @@ static async removeMember(req, res, next) {
219217
}
220218
}
221219

220+
static async acceptInvitationEmail(req, res, next) {
221+
try {
222+
const { invitationId } = req.params;
223+
224+
const result = await CodespaceService.acceptInvitationEmail(invitationId);
225+
226+
res.json({
227+
email: result.email,
228+
});
229+
} catch (error) {
230+
if (error.statusCode) {
231+
return res.status(error.statusCode).json({
232+
error: error.message,
233+
code: error.code,
234+
});
235+
}
236+
next(error);
237+
}
238+
}
239+
222240
static async createSession(req, res, next) {
223241
try {
224242
const { codespaceId, branchName } = req.body;

Codespace_Service/src/routes/codespaceRoutes.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,20 @@ router.post(
3434
CodespaceController.shareCodespaceByEmail
3535
);
3636

37-
router.get("/:id/inviteusers", validateCodespaceId, CodespaceController.getallinvitedusers);
37+
router.get(
38+
"/:id/inviteusers",
39+
validateCodespaceId,
40+
CodespaceController.getallinvitedusers
41+
);
3842
router.delete("/:id", validateCodespaceId, CodespaceController.deleteCodespace);
3943
router.put(
4044
"/accept-invitation/:invitationId",
4145
CodespaceController.acceptInvitation
4246
);
47+
router.get(
48+
"/accept-invitation-email/:invitationId",
49+
CodespaceController.acceptInvitationEmail
50+
);
4351
router.put(
4452
"/:id/github-details",
4553
validateCodespaceId,

Codespace_Service/src/services/codespaceService.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class CodespaceService {
4949
.select("id")
5050
.eq("email", email)
5151
.single();
52-
if (profileError && profileError.code !== 'PGRST116') throw profileError; // PGRST116: No rows found
52+
if (profileError && profileError.code !== "PGRST116") throw profileError; // PGRST116: No rows found
5353

5454
// If profile exists, remove from workspace_members
5555
if (profile && profile.id) {
@@ -64,13 +64,11 @@ export class CodespaceService {
6464
return { success: true };
6565
}
6666

67-
6867
static async getCodespaceDetails(userId, codespaceId) {
6968
const { data, error } = await supabase.rpc("get_codespace_details", {
7069
p_user_id: userId,
7170
p_workspace_id: codespaceId,
7271
});
73-
7472

7573
if (error) throw error;
7674

@@ -88,7 +86,6 @@ export class CodespaceService {
8886
};
8987
}
9088

91-
9289
static async getAllInvitedUsers(codespaceId) {
9390
const { data, error } = await supabase
9491
.from("invitations")
@@ -397,6 +394,24 @@ export class CodespaceService {
397394
}
398395
}
399396

397+
static async acceptInvitationEmail(invitationId) {
398+
try {
399+
const { data: invitation, error: fetchError } = await supabase
400+
.from("invitations")
401+
.select("email")
402+
.eq("id", invitationId)
403+
.single();
404+
if (fetchError || !invitation) {
405+
throw new Error("Invitation not found");
406+
}
407+
408+
return { email: invitation.email.trim() };
409+
} catch (err) {
410+
console.error("Error in acceptInvitation Email:", err);
411+
throw err;
412+
}
413+
}
414+
400415
static async acceptInvitation(invitationId) {
401416
try {
402417
// Fetch invitation

Frontend/src/App/Dashboard/AcceptInvite.tsx

Lines changed: 135 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,55 @@ const getToken = () => {
1515
return sessionData?.access_token || "";
1616
};
1717

18+
const getLoginMail = () => {
19+
const storageKey = `sb-${
20+
import.meta.env.VITE_SUPABASE_PROJECT_ID
21+
}-auth-token`;
22+
const sessionDataString = localStorage.getItem(storageKey);
23+
const sessionData = JSON.parse(sessionDataString || "null");
24+
console.log("getLoginMail sessionData:", sessionData);
25+
return sessionData?.user?.email || "";
26+
};
27+
1828
const CollaboratePage: React.FC = () => {
1929
const CODESPACE_API_URL = `${import.meta.env.VITE_BACKEND_URL}/codespaces`;
2030

2131
const navigate = useNavigate();
2232
const { invitationId } = useParams<{ invitationId: string }>();
2333
const { theme } = useTheme();
2434
const { refreshCodespaces } = useCodespaceContext();
35+
const [invitationEmail, setInvitationEmail] = useState<string | null>(null);
2536
const [error, setError] = useState<string | null>(null);
2637
const [isLoading, setIsLoading] = useState(false);
2738
const [showContent, setShowContent] = useState(false);
2839

2940
useEffect(() => {
3041
const timer = setTimeout(() => setShowContent(true), 100);
42+
43+
const getInvitationEmail = async (invitationId: string) => {
44+
try {
45+
const response = await fetch(
46+
`${CODESPACE_API_URL}/accept-invitation-email/${invitationId}`,
47+
{
48+
method: "GET",
49+
headers: {
50+
"Content-Type": "application/json",
51+
Authorization: getToken(),
52+
},
53+
}
54+
);
55+
setInvitationEmail(response.ok ? (await response.json()).email : null);
56+
} catch (error) {
57+
console.error("Error fetching invitation email:", error);
58+
}
59+
};
60+
61+
if (invitationId) {
62+
getInvitationEmail(invitationId);
63+
}
64+
3165
return () => clearTimeout(timer);
32-
}, []);
66+
}, [CODESPACE_API_URL, invitationId]);
3367

3468
const handleProceed = async () => {
3569
if (!invitationId) {
@@ -85,6 +119,88 @@ const CollaboratePage: React.FC = () => {
85119
}
86120
};
87121

122+
const loginEmail = getLoginMail();
123+
const handleLoginRedirect = () => {
124+
localStorage.setItem("invitationId", invitationId || "");
125+
navigate("/login", { state: { invitationId } });
126+
};
127+
128+
const renderActionButton = () => {
129+
if (!loginEmail) {
130+
return (
131+
<button
132+
onClick={handleLoginRedirect}
133+
className={`
134+
w-full px-4 py-2 rounded-md text-sm font-medium
135+
text-white bg-blue-600 hover:bg-blue-700
136+
transition-colors duration-200
137+
flex items-center justify-center gap-2
138+
`}
139+
>
140+
<ArrowRight className="w-4 h-4" />
141+
Login with invitation email
142+
</button>
143+
);
144+
} else if (loginEmail === invitationEmail) {
145+
return (
146+
<button
147+
onClick={handleProceed}
148+
disabled={isLoading || !invitationId}
149+
className={`
150+
w-full px-4 py-2 rounded-md text-sm font-medium
151+
text-white bg-blue-600 hover:bg-blue-700
152+
disabled:opacity-50 disabled:cursor-not-allowed
153+
transition-colors duration-200
154+
flex items-center justify-center gap-2
155+
${isLoading ? "cursor-wait" : ""}
156+
`}
157+
>
158+
{isLoading ? (
159+
<>
160+
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
161+
Processing...
162+
</>
163+
) : (
164+
<>
165+
<ArrowRight className="w-4 h-4" />
166+
Proceed
167+
</>
168+
)}
169+
</button>
170+
);
171+
} else {
172+
return (
173+
<div className="space-y-3">
174+
<div
175+
className={`p-3 ${theme.surface} border border-yellow-500 rounded-md mb-3`}
176+
>
177+
<div className="flex items-center gap-2">
178+
<AlertCircle className="w-4 h-4 text-yellow-500 flex-shrink-0" />
179+
<p className={`text-sm ${theme.text}`}>
180+
You're logged in as{" "}
181+
<span className="font-semibold">{loginEmail}</span>, but this
182+
invitation is for{" "}
183+
<span className="font-semibold">{invitationEmail}</span>
184+
</p>
185+
</div>
186+
</div>
187+
<button
188+
onClick={handleLoginRedirect}
189+
className={`
190+
w-full px-4 py-2 rounded-md text-sm font-medium
191+
text-white bg-blue-600 hover:bg-blue-700
192+
transition-colors duration-200
193+
flex items-center justify-center gap-2
194+
`}
195+
>
196+
<ArrowRight className="w-4 h-4" />
197+
Login with invitation email
198+
</button>
199+
</div>
200+
);
201+
}
202+
};
203+
88204
return (
89205
<div
90206
className={`min-h-screen flex items-center justify-center p-4 ${theme.surface}`}
@@ -139,6 +255,23 @@ const CollaboratePage: React.FC = () => {
139255
button below to join the workspace and start coding together.
140256
</p>
141257

258+
{invitationEmail && (
259+
<div
260+
className={`p-3 ${theme.surface} border ${theme.border} rounded-md mb-3`}
261+
>
262+
<p
263+
className={`text-xs ${theme.textSecondary} mb-2 uppercase tracking-wide`}
264+
>
265+
Invitation Email
266+
</p>
267+
<code
268+
className={`text-sm font-mono ${theme.text} block break-all`}
269+
>
270+
{invitationEmail}
271+
</code>
272+
</div>
273+
)}
274+
142275
{invitationId && (
143276
<div
144277
className={`p-3 ${theme.surface} border ${theme.border} rounded-md`}
@@ -158,32 +291,7 @@ const CollaboratePage: React.FC = () => {
158291
</div>
159292

160293
{/* Action Buttons */}
161-
<div className="space-y-3">
162-
<button
163-
onClick={handleProceed}
164-
disabled={isLoading || !invitationId}
165-
className={`
166-
w-full px-4 py-2 rounded-md text-sm font-medium
167-
text-white bg-blue-600 hover:bg-blue-700
168-
disabled:opacity-50 disabled:cursor-not-allowed
169-
transition-colors duration-200
170-
flex items-center justify-center gap-2
171-
${isLoading ? "cursor-wait" : ""}
172-
`}
173-
>
174-
{isLoading ? (
175-
<>
176-
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
177-
Processing...
178-
</>
179-
) : (
180-
<>
181-
<ArrowRight className="w-4 h-4" />
182-
Accept Invitation
183-
</>
184-
)}
185-
</button>
186-
</div>
294+
<div className="space-y-3">{renderActionButton()}</div>
187295

188296
{/* Status indicator */}
189297
<div className={`mt-4 pt-4 border-t ${theme.border}`}>

0 commit comments

Comments
 (0)