Skip to content

Commit ac85566

Browse files
committed
feat: initial authorizaton of accounts
1 parent 0c08f16 commit ac85566

File tree

4 files changed

+225
-26
lines changed

4 files changed

+225
-26
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import createClient from "@/utils/supabase/api";
2+
import { NextApiRequest, NextApiResponse } from "next";
3+
import { AccountsFromDB } from "../calendar";
4+
5+
export default async function handler(
6+
req: NextApiRequest,
7+
res: NextApiResponse
8+
) {
9+
if (req.method === "POST") {
10+
const { accessToken } = req.body;
11+
console.log("🚀 ~ accessToken:", accessToken);
12+
13+
try {
14+
// Fetch user ID from Facebook
15+
const accountsResponse = await fetch(
16+
`https://graph.facebook.com/v21.0/me/accounts?access_token=${accessToken}`
17+
);
18+
const accountsData = await accountsResponse.json();
19+
20+
// Save data to Supabase
21+
const supabase = createClient(req, res);
22+
const { data, error } = await supabase
23+
.from("accounts")
24+
.upsert(mapAccountsData(accountsData?.data), {
25+
onConflict: "account_id",
26+
});
27+
28+
if (error) throw error;
29+
30+
res.status(200).json({ success: true, data });
31+
} catch (error) {
32+
console.error("Error:", error);
33+
res.status(500).json({
34+
success: false,
35+
error: "An error occurred while processing your request.",
36+
});
37+
}
38+
} else {
39+
res.setHeader("Allow", ["POST"]);
40+
res.status(405).end(`Method ${req.method} Not Allowed`);
41+
}
42+
}
43+
44+
type FacebookAccountData = {
45+
access_token: string;
46+
category: string;
47+
category_list: {
48+
data: {
49+
id: string;
50+
name: string;
51+
}[];
52+
};
53+
id: string;
54+
name: string;
55+
tasks: string[];
56+
}[];
57+
58+
function mapAccountsData(
59+
accountsData: FacebookAccountData
60+
): Omit<AccountsFromDB, "id">[] {
61+
return accountsData.map((account) => ({
62+
account_id: Number(account.id),
63+
access_token: account.access_token,
64+
name: account.name,
65+
}));
66+
}

apps/web/src/pages/authorize.tsx

Lines changed: 100 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { useRouter } from "next/router";
22
import { useEffect, useState } from "react";
33

44
export default function Authorize() {
5+
const [isChecked, setIsChecked] = useState(false);
56
const [authUrl, setAuthUrl] = useState("");
67
const router = useRouter();
8+
const [isLoading, setIsLoading] = useState(false);
79

810
useEffect(() => {
911
// Replace with your Facebook app's client ID and redirect URI
@@ -13,42 +15,114 @@ export default function Authorize() {
1315

1416
const url = `https://www.facebook.com/v11.0/dialog/oauth?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=token`;
1517
setAuthUrl(url);
18+
19+
// Check if there's an access token in the URL fragment
20+
const hash = window.location.hash.substring(1);
21+
const params = new URLSearchParams(hash);
22+
const accessToken = params.get("access_token");
23+
24+
if (accessToken) {
25+
handleAccessToken(accessToken);
26+
}
1627
}, []);
1728

29+
const handleAccessToken = async (accessToken: string) => {
30+
setIsLoading(true); // Set loading to true when the request starts
31+
try {
32+
const response = await fetch("/api/facebook-auth", {
33+
method: "POST",
34+
headers: {
35+
"Content-Type": "application/json",
36+
},
37+
body: JSON.stringify({ accessToken }),
38+
});
39+
const data = await response.json();
40+
41+
if (data.success) {
42+
router.push({
43+
pathname: "/authorize/success",
44+
query: { details: JSON.stringify(data.data) },
45+
});
46+
} else {
47+
console.error("Error saving account:", data.error);
48+
router.push({
49+
pathname: "/authorize/error",
50+
query: { message: data.error },
51+
});
52+
}
53+
} catch (error) {
54+
console.error("Error:", error);
55+
router.push({
56+
pathname: "/authorize/error",
57+
query: { message: "An unexpected error occurred. Please try again." },
58+
});
59+
} finally {
60+
setIsLoading(false); // Set loading to false when the request completes
61+
}
62+
};
63+
1864
const handleAuthorize = () => {
19-
router.push(authUrl);
65+
window.location.href = authUrl;
2066
};
2167

2268
return (
2369
<div className="flex flex-col items-center justify-center min-h-screen p-12">
24-
<div className="w-full max-w-2xl text-center">
25-
<h1 className="text-3xl font-bold mb-4 text-center">
26-
Authorize Facebook App
27-
</h1>
28-
<p className="mb-6 text-center">
29-
To provide you with the best experience, we need access to your
30-
Facebook pages. By authorizing our app, you allow us to manage and
31-
retrieve events from your pages, which helps us keep your event
32-
information up-to-date and accurate in our database.
33-
</p>
34-
<p className="my-6 text-center">
35-
Click the button below whenever you are ready!
36-
</p>
37-
<div className="flex justify-center">
70+
{isLoading ? (
71+
<>
72+
<div className="w-full max-w-2xl text-center">
73+
<h1 className="text-3xl font-bold mb-4 text-center">
74+
Just a sec...
75+
</h1>
76+
<p className="mb-6 text-center text-gray-500">
77+
We are processing your request. This may take a few seconds.
78+
</p>
79+
</div>
80+
<button
81+
className="text-sm mt-2 text-gray-500 absolute bottom-5"
82+
onClick={() => router.push("/")}
83+
>
84+
Click here to go back if nothing happens...
85+
</button>
86+
</>
87+
) : (
88+
<>
89+
<div className="w-full max-w-2xl text-center">
90+
<h1 className="text-3xl font-bold mb-4 text-center">
91+
Authorize CebEvents FB App
92+
</h1>
93+
<p className="mb-6 text-center">
94+
By authorizing our app, you allow us to manage and retrieve events
95+
from your pages, which helps us keep your event information
96+
up-to-date and accurate in this app and our database.
97+
</p>
98+
<div className="my-6 text-center">
99+
<label className="inline-flex items-center">
100+
<input
101+
type="checkbox"
102+
className="form-checkbox"
103+
onChange={(e) => setIsChecked(e.target.checked)}
104+
/>
105+
<span className="ml-2">I am ready to proceed...</span>
106+
</label>
107+
</div>
108+
<div className="flex justify-center">
109+
<button
110+
onClick={handleAuthorize}
111+
className={`px-4 py-2 rounded-lg text-white ${isChecked ? "bg-blue-500" : "bg-gray-400 cursor-not-allowed"}`}
112+
disabled={!isChecked || isLoading} // Disable button when loading
113+
>
114+
Begin Authorization
115+
</button>
116+
</div>
117+
</div>
38118
<button
39-
onClick={handleAuthorize}
40-
className="px-4 py-2 bg-blue-500 text-white rounded-lg"
119+
className="text-sm mt-2 text-gray-500 absolute bottom-5"
120+
onClick={() => router.push("/")}
41121
>
42-
Begin Authorization
122+
Click here to go back
43123
</button>
44-
</div>
45-
<button
46-
className="text-sm mt-2 text-gray-500"
47-
onClick={() => router.push("/")}
48-
>
49-
Click here to go back
50-
</button>
51-
</div>
124+
</>
125+
)}
52126
</div>
53127
);
54128
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useRouter } from "next/router";
2+
3+
export default function AuthorizeError() {
4+
const router = useRouter();
5+
6+
return (
7+
<div className="flex flex-col items-center justify-center min-h-screen p-12">
8+
<div className="w-full max-w-2xl text-center">
9+
<h1 className="text-3xl font-bold mb-4 text-center text-red-600">
10+
Authorization Error
11+
</h1>
12+
<p className="mb-6 text-center">
13+
There was an error during the authorization process:
14+
</p>
15+
<button
16+
className="px-4 py-2 bg-blue-500 text-white rounded-lg mr-4"
17+
onClick={() => router.push("/authorize")}
18+
>
19+
Try Again
20+
</button>
21+
<button
22+
className="px-4 py-2 bg-gray-500 text-white rounded-lg"
23+
onClick={() => router.push("/")}
24+
>
25+
Go Home
26+
</button>
27+
</div>
28+
</div>
29+
);
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { useRouter } from "next/router";
2+
3+
export default function AuthorizeSuccess() {
4+
const router = useRouter();
5+
6+
return (
7+
<div className="flex flex-col items-center justify-center min-h-screen p-12">
8+
<div className="w-full max-w-2xl text-center">
9+
<h1 className="text-3xl font-bold mb-4 text-center text-green-600">
10+
Authorization Successful!
11+
</h1>
12+
<p className="mb-6 text-center">
13+
Awesome! Your Facebook account has been successfully connected to
14+
CebEvents.
15+
</p>
16+
<p className="mb-6 text-center text-gray-500">
17+
Please note that events might show up later as we run our automation
18+
every 15 minutes.
19+
</p>
20+
<button
21+
className="px-4 py-2 bg-blue-500 text-white rounded-lg"
22+
onClick={() => router.push("/")}
23+
>
24+
Go to Dashboard
25+
</button>
26+
</div>
27+
</div>
28+
);
29+
}

0 commit comments

Comments
 (0)