+6
-216
app/page.tsx
+6
-216
app/page.tsx
···
1
-
"use client";
2
-
3
-
import { AtSign } from "lucide-react";
4
-
import { Manrope } from "next/font/google";
5
-
import { useSearchParams } from "next/navigation";
6
-
import { useEffect, useState } from "react";
7
-
8
-
const manrope = Manrope({
9
-
preload: true,
10
-
subsets: ["latin"],
11
-
weight: ["300"]
12
-
});
1
+
import HomeContent from "@/components/HomeContent";
2
+
import { Suspense } from "react";
13
3
14
4
const Home = () => {
15
-
const searchParams = useSearchParams();
16
-
17
-
const [atProtoIdentifier, setAtProtoIdentifier] = useState("");
18
-
const [followCounts, setFollowCounts] = useState<number>(0);
19
-
const [isCounting, setIsCounting] = useState(false);
20
-
const [nonMutualCount, setNonMutualCount] = useState<null | number>(null);
21
-
const [step, setStep] = useState<
22
-
"last" | "login" | "nonMutuals" | "permission" | "stats"
23
-
>("login");
24
-
const [unfollowCount, setUnfollowCount] = useState(0);
25
-
26
-
useEffect(() => {
27
-
const authStatus = searchParams.get("auth_status");
28
-
29
-
if (authStatus === "success") {
30
-
const follows = searchParams.get("follows");
31
-
32
-
setFollowCounts(Number(follows));
33
-
setStep("stats");
34
-
35
-
window.history.replaceState(null, "", window.location.pathname);
36
-
}
37
-
}, [searchParams]);
38
-
39
-
const ebbAndFollow = async () => {
40
-
try {
41
-
const response = await fetch("/api/ebbAndFollow/", {
42
-
credentials: "include",
43
-
headers: { "Content-Type": "application/json" },
44
-
method: "POST"
45
-
});
46
-
const data = await response.json();
47
-
48
-
if (response.ok && data.success) {
49
-
setUnfollowCount(data.unfollowCount);
50
-
setStep("last");
51
-
}
52
-
} catch (error) {}
53
-
};
54
-
55
-
const fetchNonMutuals = async () => {
56
-
setNonMutualCount(null);
57
-
setIsCounting(true);
58
-
59
-
try {
60
-
const response = await fetch("/api/ebbAndFollow/count", {
61
-
credentials: "include"
62
-
});
63
-
const data = await response.json();
64
-
65
-
if (response.ok && data.success) {
66
-
setNonMutualCount(data.nonMutualCount);
67
-
setStep("nonMutuals");
68
-
} else {
69
-
alert(`Failed to fetch non-mutual count: ${data.error}`);
70
-
}
71
-
} catch (error) {
72
-
alert("A network error occurred while calculating the ebb.");
73
-
} finally {
74
-
setIsCounting(false);
75
-
}
76
-
};
77
-
78
-
const loginToATProto = async () => {
79
-
console.log(atProtoIdentifier);
80
-
81
-
try {
82
-
const response = await fetch("/api/auth/atproto/login", {
83
-
body: JSON.stringify({
84
-
atProtoIdentifier
85
-
}),
86
-
headers: { "Content-Type": "application/json" },
87
-
method: "POST"
88
-
});
89
-
const data = await response.json();
90
-
91
-
if (response.ok && data.url) {
92
-
window.location.href = data.url;
93
-
} else {
94
-
console.error("Login initiation failed:", data.error);
95
-
}
96
-
} catch (error) {
97
-
console.error("Error initiating AT Protocol OAuth:", error);
98
-
}
99
-
};
100
-
101
-
const getStepRender = () => {
102
-
switch (step) {
103
-
case "last":
104
-
return (
105
-
<>
106
-
<h2 className={`${manrope.className} text-4xl`}>
107
-
Congrats!
108
-
</h2>
109
-
<h2 className={`${manrope.className} text-4xl`}>
110
-
Of those {nonMutualCount} non mutuals,
111
-
</h2>
112
-
<h2 className={`${manrope.className} text-4xl`}>
113
-
{unfollowCount} were unfollowed!
114
-
</h2>
115
-
</>
116
-
);
117
-
case "login":
118
-
return (
119
-
<>
120
-
<h1 className={`${manrope.className} text-4xl`}>
121
-
ebb&follow
122
-
</h1>
123
-
<div className="border flex gap-2 h-12 items-center p-2 rounded-lg w-1/4">
124
-
<AtSign />
125
-
<input
126
-
className="flex-1 outline-none"
127
-
onChange={(e) =>
128
-
setAtProtoIdentifier(e.target.value)
129
-
}
130
-
placeholder="Enter your ATProto handle or DID"
131
-
value={atProtoIdentifier}
132
-
/>
133
-
</div>
134
-
<button
135
-
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/8"
136
-
onClick={loginToATProto}
137
-
>
138
-
Log In
139
-
</button>
140
-
</>
141
-
);
142
-
case "nonMutuals":
143
-
return (
144
-
<>
145
-
<h2 className={`${manrope.className} text-4xl`}>
146
-
Of those {followCounts} accounts you follow,
147
-
</h2>
148
-
<h2 className={`${manrope.className} text-4xl`}>
149
-
{nonMutualCount} are nonMutuals*
150
-
</h2>
151
-
<h3>
152
-
*There may be a discrepancy in count between the
153
-
follower counts provided by the PDS and by the
154
-
AppView
155
-
</h3>
156
-
<button
157
-
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/8"
158
-
onClick={() => setStep("permission")}
159
-
>
160
-
Next
161
-
</button>
162
-
</>
163
-
);
164
-
case "permission":
165
-
return (
166
-
<>
167
-
<h2 className={`${manrope.className} text-4xl`}>
168
-
For each of those {nonMutualCount} nonMutuals,
169
-
</h2>
170
-
<h2 className={`${manrope.className} text-4xl`}>
171
-
ebb&follow will randomly choose to either continue
172
-
following or unfollow
173
-
</h2>
174
-
<h2 className={`${manrope.className} text-4xl`}>
175
-
No algorithm, just a coin flip
176
-
</h2>
177
-
<h2 className={`${manrope.className} text-4xl`}>
178
-
Do you wish to continue?
179
-
</h2>
180
-
<div className="flex justify-around w-1/2">
181
-
<button
182
-
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/4"
183
-
onClick={() => setStep("login")}
184
-
>
185
-
No
186
-
</button>
187
-
<button
188
-
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/4"
189
-
onClick={ebbAndFollow}
190
-
>
191
-
Yes
192
-
</button>
193
-
</div>
194
-
</>
195
-
);
196
-
case "stats":
197
-
return (
198
-
<>
199
-
<h2 className={`${manrope.className} text-4xl`}>
200
-
You follow {followCounts} accounts
201
-
</h2>
202
-
<button
203
-
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/8"
204
-
onClick={fetchNonMutuals}
205
-
>
206
-
{isCounting
207
-
? "Counting non-mutuals..."
208
-
: "Count non-mutuals"}
209
-
</button>
210
-
</>
211
-
);
212
-
}
213
-
};
214
-
215
5
return (
216
-
<div className="bg-linear-to-br flex flex-col from-[#0081A7] gap-4 items-center justify-center to-[#FDFCDC] via-[#00AFB9] h-screen w-screen">
217
-
{getStepRender()}
218
-
</div>
6
+
<Suspense fallback={<div>Loading...</div>}>
7
+
<HomeContent />
8
+
</Suspense>
219
9
);
220
10
};
221
11
222
-
export default Home;
12
+
export default Home;
+222
components/HomeContent.tsx
+222
components/HomeContent.tsx
···
1
+
"use client";
2
+
3
+
import { AtSign } from "lucide-react";
4
+
import { Manrope } from "next/font/google";
5
+
import { useSearchParams } from "next/navigation";
6
+
import { useEffect, useState } from "react";
7
+
8
+
const manrope = Manrope({
9
+
preload: true,
10
+
subsets: ["latin"],
11
+
weight: ["300"]
12
+
});
13
+
14
+
const HomeContent = () => {
15
+
const searchParams = useSearchParams();
16
+
17
+
const [atProtoIdentifier, setAtProtoIdentifier] = useState("");
18
+
const [followCounts, setFollowCounts] = useState<number>(0);
19
+
const [isCounting, setIsCounting] = useState(false);
20
+
const [nonMutualCount, setNonMutualCount] = useState<null | number>(null);
21
+
const [step, setStep] = useState<
22
+
"last" | "login" | "nonMutuals" | "permission" | "stats"
23
+
>("login");
24
+
const [unfollowCount, setUnfollowCount] = useState(0);
25
+
26
+
useEffect(() => {
27
+
const authStatus = searchParams.get("auth_status");
28
+
29
+
if (authStatus === "success") {
30
+
const follows = searchParams.get("follows");
31
+
32
+
setFollowCounts(Number(follows));
33
+
setStep("stats");
34
+
35
+
window.history.replaceState(null, "", window.location.pathname);
36
+
}
37
+
}, [searchParams]);
38
+
39
+
const ebbAndFollow = async () => {
40
+
try {
41
+
const response = await fetch("/api/ebbAndFollow/", {
42
+
credentials: "include",
43
+
headers: { "Content-Type": "application/json" },
44
+
method: "POST"
45
+
});
46
+
const data = await response.json();
47
+
48
+
if (response.ok && data.success) {
49
+
setUnfollowCount(data.unfollowCount);
50
+
setStep("last");
51
+
}
52
+
} catch (error) {}
53
+
};
54
+
55
+
const fetchNonMutuals = async () => {
56
+
setNonMutualCount(null);
57
+
setIsCounting(true);
58
+
59
+
try {
60
+
const response = await fetch("/api/ebbAndFollow/count", {
61
+
credentials: "include"
62
+
});
63
+
const data = await response.json();
64
+
65
+
if (response.ok && data.success) {
66
+
setNonMutualCount(data.nonMutualCount);
67
+
setStep("nonMutuals");
68
+
} else {
69
+
alert(`Failed to fetch non-mutual count: ${data.error}`);
70
+
}
71
+
} catch (error) {
72
+
alert("A network error occurred while calculating the ebb.");
73
+
} finally {
74
+
setIsCounting(false);
75
+
}
76
+
};
77
+
78
+
const loginToATProto = async () => {
79
+
console.log(atProtoIdentifier);
80
+
81
+
try {
82
+
const response = await fetch("/api/auth/atproto/login", {
83
+
body: JSON.stringify({
84
+
atProtoIdentifier
85
+
}),
86
+
headers: { "Content-Type": "application/json" },
87
+
method: "POST"
88
+
});
89
+
const data = await response.json();
90
+
91
+
if (response.ok && data.url) {
92
+
window.location.href = data.url;
93
+
} else {
94
+
console.error("Login initiation failed:", data.error);
95
+
}
96
+
} catch (error) {
97
+
console.error("Error initiating AT Protocol OAuth:", error);
98
+
}
99
+
};
100
+
101
+
const getStepRender = () => {
102
+
switch (step) {
103
+
case "last":
104
+
return (
105
+
<>
106
+
<h2 className={`${manrope.className} text-4xl`}>
107
+
Congrats!
108
+
</h2>
109
+
<h2 className={`${manrope.className} text-4xl`}>
110
+
Of those {nonMutualCount} non mutuals,
111
+
</h2>
112
+
<h2 className={`${manrope.className} text-4xl`}>
113
+
{unfollowCount} were unfollowed!
114
+
</h2>
115
+
</>
116
+
);
117
+
case "login":
118
+
return (
119
+
<>
120
+
<h1 className={`${manrope.className} text-4xl`}>
121
+
ebb&follow
122
+
</h1>
123
+
<div className="border flex gap-2 h-12 items-center p-2 rounded-lg w-1/4">
124
+
<AtSign />
125
+
<input
126
+
className="flex-1 outline-none"
127
+
onChange={(e) =>
128
+
setAtProtoIdentifier(e.target.value)
129
+
}
130
+
placeholder="Enter your ATProto handle or DID"
131
+
value={atProtoIdentifier}
132
+
/>
133
+
</div>
134
+
<button
135
+
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/8"
136
+
onClick={loginToATProto}
137
+
>
138
+
Log In
139
+
</button>
140
+
</>
141
+
);
142
+
case "nonMutuals":
143
+
return (
144
+
<>
145
+
<h2 className={`${manrope.className} text-4xl`}>
146
+
Of those {followCounts} accounts you follow,
147
+
</h2>
148
+
<h2 className={`${manrope.className} text-4xl`}>
149
+
{nonMutualCount} are nonMutuals*
150
+
</h2>
151
+
<h3>
152
+
*There may be a discrepancy in count between the
153
+
follower counts provided by the PDS and by the
154
+
AppView
155
+
</h3>
156
+
<button
157
+
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/8"
158
+
onClick={() => setStep("permission")}
159
+
>
160
+
Next
161
+
</button>
162
+
</>
163
+
);
164
+
case "permission":
165
+
return (
166
+
<>
167
+
<h2 className={`${manrope.className} text-4xl`}>
168
+
For each of those {nonMutualCount} nonMutuals,
169
+
</h2>
170
+
<h2 className={`${manrope.className} text-4xl`}>
171
+
ebb&follow will randomly choose to either continue
172
+
following or unfollow
173
+
</h2>
174
+
<h2 className={`${manrope.className} text-4xl`}>
175
+
No algorithm, just a coin flip
176
+
</h2>
177
+
<h2 className={`${manrope.className} text-4xl`}>
178
+
Do you wish to continue?
179
+
</h2>
180
+
<div className="flex justify-around w-1/2">
181
+
<button
182
+
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/4"
183
+
onClick={() => setStep("login")}
184
+
>
185
+
No
186
+
</button>
187
+
<button
188
+
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/4"
189
+
onClick={ebbAndFollow}
190
+
>
191
+
Yes
192
+
</button>
193
+
</div>
194
+
</>
195
+
);
196
+
case "stats":
197
+
return (
198
+
<>
199
+
<h2 className={`${manrope.className} text-4xl`}>
200
+
You follow {followCounts} accounts
201
+
</h2>
202
+
<button
203
+
className="bg-white/90 active:bg-white hover:cursor-pointer h-12 rounded-lg text-xl text-[#00AFB9] w-1/8"
204
+
onClick={fetchNonMutuals}
205
+
>
206
+
{isCounting
207
+
? "Counting non-mutuals..."
208
+
: "Count non-mutuals"}
209
+
</button>
210
+
</>
211
+
);
212
+
}
213
+
};
214
+
215
+
return (
216
+
<div className="bg-linear-to-br flex flex-col from-[#0081A7] gap-4 items-center justify-center to-[#FDFCDC] via-[#00AFB9] h-screen w-screen">
217
+
{getStepRender()}
218
+
</div>
219
+
);
220
+
};
221
+
222
+
export default HomeContent;