Highly ambitious ATProtocol AppView service and sdks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

update waitlist flow

+179 -26
+6 -6
frontend/src/features/auth/handlers.tsx
··· 262 262 263 263 if (!code || !state) { 264 264 return Response.redirect( 265 - new URL("/?error=invalid_callback", req.url), 265 + new URL("/waitlist?error=invalid_callback", req.url), 266 266 302, 267 267 ); 268 268 } ··· 281 281 282 282 if (!atprotoClient.oauth) { 283 283 return Response.redirect( 284 - new URL("/?error=oauth_not_configured", req.url), 284 + new URL("/waitlist?error=oauth_not_configured", req.url), 285 285 302, 286 286 ); 287 287 } ··· 293 293 const userInfo = await atprotoClient.oauth.getUserInfo(); 294 294 295 295 if (!userInfo) { 296 - return Response.redirect(new URL("/?error=no_user_info", req.url), 302); 296 + return Response.redirect(new URL("/waitlist?error=no_user_info", req.url), 302); 297 297 } 298 298 299 299 // Create waitlist record ··· 318 318 // Clear OAuth session since this is just for waitlist 319 319 await atprotoClient.oauth?.logout(); 320 320 321 - // Redirect back to landing page with success parameter 321 + // Redirect back to waitlist page with success parameter 322 322 const handle = userInfo.name || waitlistData.handle || "user"; 323 - const redirectUrl = new URL("/", req.url); 323 + const redirectUrl = new URL("/waitlist", req.url); 324 324 redirectUrl.searchParams.set("waitlist", "success"); 325 325 redirectUrl.searchParams.set("handle", handle); 326 326 return Response.redirect(redirectUrl.toString(), 302); 327 327 } catch (error) { 328 328 console.error("Waitlist callback error:", error); 329 - return Response.redirect(new URL("/?error=waitlist_failed", req.url), 302); 329 + return Response.redirect(new URL("/waitlist?error=waitlist_failed", req.url), 302); 330 330 } 331 331 } 332 332
-7
frontend/src/features/landing/handlers.tsx
··· 8 8 async function handleLandingPage(req: Request): Promise<Response> { 9 9 const context = await withAuth(req); 10 10 11 - // Check for waitlist success parameters 12 - const url = new URL(req.url); 13 - const waitlistSuccess = url.searchParams.get("waitlist") === "success"; 14 - const handle = url.searchParams.get("handle"); 15 - 16 11 // Fetch timeline slices 17 12 const slices = await getTimeline(publicClient, 20); 18 13 19 14 return renderHTML( 20 15 <LandingPage 21 - waitlistSuccess={waitlistSuccess} 22 - handle={handle || undefined} 23 16 currentUser={context.currentUser} 24 17 slices={slices} 25 18 />,
-9
frontend/src/features/landing/templates/LandingPage.tsx
··· 1 1 import { Layout } from "../../../shared/fragments/Layout.tsx"; 2 - import { WaitlistFormModal } from "./fragments/WaitlistFormModal.tsx"; 3 - import { WaitlistSuccessModal } from "./fragments/WaitlistSuccessModal.tsx"; 4 2 import { SliceCard } from "../../../shared/fragments/SliceCard.tsx"; 5 3 import type { AuthenticatedUser } from "../../../routes/middleware.ts"; 6 4 import type { NetworkSlicesSliceDefsSliceView } from "../../../client.ts"; 7 5 8 6 interface LandingPageProps { 9 - waitlistSuccess?: boolean; 10 - handle?: string; 11 7 currentUser?: AuthenticatedUser; 12 8 slices?: NetworkSlicesSliceDefsSliceView[]; 13 9 } 14 10 15 11 export function LandingPage({ 16 - waitlistSuccess, 17 - handle, 18 12 currentUser, 19 13 slices = [], 20 14 }: LandingPageProps = {}) { ··· 55 49 </div> 56 50 )} 57 51 58 - {/* Modals */} 59 - <WaitlistFormModal /> 60 - <WaitlistSuccessModal handle={handle} show={waitlistSuccess} /> 61 52 </div> 62 53 </Layout> 63 54 );
+31
frontend/src/features/waitlist/handlers.tsx
··· 1 + import type { Route } from "@std/http/unstable-route"; 2 + import { withAuth } from "../../routes/middleware.ts"; 3 + import { renderHTML } from "../../utils/render.tsx"; 4 + import { WaitlistPage } from "./templates/WaitlistPage.tsx"; 5 + 6 + async function handleWaitlistPage(req: Request): Promise<Response> { 7 + const context = await withAuth(req); 8 + const url = new URL(req.url); 9 + 10 + // Check for success parameter from callback 11 + const success = url.searchParams.get("waitlist") === "success"; 12 + const handle = url.searchParams.get("handle"); 13 + const error = url.searchParams.get("error"); 14 + 15 + return renderHTML( 16 + <WaitlistPage 17 + success={success} 18 + handle={handle || undefined} 19 + error={error || undefined} 20 + currentUser={context.currentUser} 21 + /> 22 + ); 23 + } 24 + 25 + export const waitlistRoutes: Route[] = [ 26 + { 27 + method: "GET", 28 + pattern: new URLPattern({ pathname: "/waitlist" }), 29 + handler: handleWaitlistPage, 30 + }, 31 + ];
+42
frontend/src/features/waitlist/templates/WaitlistPage.tsx
··· 1 + import { Layout } from "../../../shared/fragments/Layout.tsx"; 2 + import { WaitlistForm } from "./fragments/WaitlistForm.tsx"; 3 + import { WaitlistSuccess } from "./fragments/WaitlistSuccess.tsx"; 4 + import type { AuthenticatedUser } from "../../../routes/middleware.ts"; 5 + 6 + interface WaitlistPageProps { 7 + success?: boolean; 8 + handle?: string; 9 + error?: string; 10 + currentUser?: AuthenticatedUser; 11 + } 12 + 13 + export function WaitlistPage({ 14 + success, 15 + handle, 16 + error, 17 + currentUser, 18 + }: WaitlistPageProps) { 19 + return ( 20 + <Layout title="Join the Waitlist - Slices" currentUser={currentUser}> 21 + <div className="min-h-screen bg-white flex items-center justify-center px-4 py-16"> 22 + <div className="w-full max-w-md"> 23 + {success ? ( 24 + <WaitlistSuccess handle={handle} /> 25 + ) : ( 26 + <> 27 + <div className="text-center mb-8"> 28 + <h1 className="text-4xl font-bold text-zinc-900 mb-4"> 29 + Join the Slices Waitlist 30 + </h1> 31 + <p className="text-lg text-zinc-600"> 32 + Be among the first to experience the future of AT Protocol ecosystem tools. 33 + </p> 34 + </div> 35 + <WaitlistForm error={error} /> 36 + </> 37 + )} 38 + </div> 39 + </div> 40 + </Layout> 41 + ); 42 + }
+48
frontend/src/features/waitlist/templates/fragments/WaitlistForm.tsx
··· 1 + import { Button } from "../../../../shared/fragments/Button.tsx"; 2 + import { Input } from "../../../../shared/fragments/Input.tsx"; 3 + 4 + interface WaitlistFormProps { 5 + error?: string; 6 + } 7 + 8 + export function WaitlistForm({ error }: WaitlistFormProps) { 9 + return ( 10 + <div className="bg-white p-8 border border-zinc-200"> 11 + <form action="/auth/waitlist/initiate" method="POST"> 12 + <div className="space-y-6"> 13 + {error && ( 14 + <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3"> 15 + {error === "oauth_not_configured" 16 + ? "OAuth is not configured. Please try again later." 17 + : error === "invalid_callback" 18 + ? "Invalid authorization callback." 19 + : error === "no_user_info" 20 + ? "Could not retrieve user information." 21 + : error === "waitlist_failed" 22 + ? "Failed to join waitlist. Please try again." 23 + : "An error occurred. Please try again."} 24 + </div> 25 + )} 26 + 27 + <Input 28 + label="Your handle" 29 + name="handle" 30 + placeholder="alice.bsky.social" 31 + helpText="Enter your AT Protocol handle to join the waitlist" 32 + required 33 + /> 34 + 35 + <div className="space-y-4"> 36 + <Button type="submit" variant="primary" class="w-full justify-center"> 37 + Join Waitlist 38 + </Button> 39 + 40 + <p className="text-xs text-zinc-500 text-center"> 41 + By joining the waitlist, you'll be notified when Slices is ready for you. 42 + </p> 43 + </div> 44 + </div> 45 + </form> 46 + </div> 47 + ); 48 + }
+45
frontend/src/features/waitlist/templates/fragments/WaitlistSuccess.tsx
··· 1 + import { Button } from "../../../../shared/fragments/Button.tsx"; 2 + import { Check } from "lucide-preact"; 3 + 4 + interface WaitlistSuccessProps { 5 + handle?: string; 6 + } 7 + 8 + export function WaitlistSuccess({ handle }: WaitlistSuccessProps) { 9 + return ( 10 + <div className="bg-white p-8 border border-zinc-200 text-center"> 11 + <div className="flex justify-center mb-6"> 12 + <div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center"> 13 + <Check size={32} className="text-green-600" /> 14 + </div> 15 + </div> 16 + 17 + <h2 className="text-2xl font-bold text-zinc-900 mb-4"> 18 + You're on the List! 19 + </h2> 20 + 21 + <p className="text-zinc-600 mb-6"> 22 + Thanks for joining the waitlist{handle ? <>, <span className="font-bold">{handle}</span></> : ""}! We'll notify you as soon as Slices is ready for you. 23 + </p> 24 + 25 + <div className="space-y-4"> 26 + <Button href="/" variant="primary" class="w-full justify-center"> 27 + Back to Home 28 + </Button> 29 + 30 + <p className="text-sm text-zinc-500"> 31 + In the meantime, follow us{" "} 32 + <a 33 + href="https://bsky.app/profile/slices.network" 34 + target="_blank" 35 + rel="noopener noreferrer" 36 + className="text-blue-500 hover:text-blue-600 underline" 37 + > 38 + @slices.network 39 + </a>{" "} 40 + for updates and sneak peeks. 41 + </p> 42 + </div> 43 + </div> 44 + ); 45 + }
+4
frontend/src/routes/mod.ts
··· 1 1 import type { Route } from "@std/http/unstable-route"; 2 2 import { landingRoutes } from "../features/landing/handlers.tsx"; 3 3 import { authRoutes } from "../features/auth/handlers.tsx"; 4 + import { waitlistRoutes } from "../features/waitlist/handlers.tsx"; 4 5 import { dashboardRoutes } from "../features/dashboard/handlers.tsx"; 5 6 import { 6 7 apiDocsRoutes, ··· 23 24 24 25 // Auth routes (login, oauth, logout) 25 26 ...authRoutes, 27 + 28 + // Waitlist page 29 + ...waitlistRoutes, 26 30 27 31 // Documentation routes 28 32 ...docsRoutes,
+3 -4
frontend/src/shared/fragments/Layout.tsx
··· 156 156 ) 157 157 : ( 158 158 <div className="flex items-center space-x-2"> 159 - <button 160 - type="button" 159 + <a 160 + href="/waitlist" 161 161 className="px-3 py-1.5 text-sm text-zinc-600 hover:text-zinc-900 hover:bg-zinc-100 rounded-md transition-colors" 162 - _="on click call #waitlist-modal.showModal()" 163 162 > 164 163 Join Waitlist 165 - </button> 164 + </a> 166 165 <a 167 166 href="/login" 168 167 className="px-4 py-1.5 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors"