identity-gate.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. "use client";
  2. import {
  3. useCallback,
  4. useEffect,
  5. useState,
  6. type ReactNode,
  7. } from "react";
  8. import { usePortalAuth } from "@/components/auth/portal-auth-provider";
  9. import {
  10. clearPortalSession,
  11. fetchPortalSession,
  12. type AuthMethod,
  13. } from "@/lib/portal-session";
  14. export type PortalIdentity = {
  15. isLoading: boolean;
  16. isSignedIn: boolean;
  17. label: string | null;
  18. source: "briven" | "demo" | "wallet" | "konnos" | null;
  19. userId: string | null;
  20. walletAddress: string | null;
  21. method: AuthMethod | null;
  22. signOut: () => Promise<void>;
  23. };
  24. /**
  25. * Identity for dashboard gates. Any login method (email OTP, Konnos, MetaMask,
  26. * later mavi) mints first-party /api/session so the user stays signed in.
  27. */
  28. export function IdentityConsumer({
  29. children,
  30. }: {
  31. children: (identity: PortalIdentity) => ReactNode;
  32. }) {
  33. const { brivenConfigured } = usePortalAuth();
  34. if (brivenConfigured) {
  35. return <SessionIdentityConsumer>{children}</SessionIdentityConsumer>;
  36. }
  37. return <DemoIdentityConsumer>{children}</DemoIdentityConsumer>;
  38. }
  39. function DemoIdentityConsumer({
  40. children,
  41. }: {
  42. children: (identity: PortalIdentity) => ReactNode;
  43. }) {
  44. const portal = usePortalAuth();
  45. const identity: PortalIdentity = {
  46. isLoading: false,
  47. isSignedIn: Boolean(portal.demoSession),
  48. label: portal.demoSession?.label ?? null,
  49. source: portal.demoSession?.kind ?? null,
  50. userId: portal.demoSession?.userId ?? null,
  51. walletAddress: portal.demoSession?.walletAddress ?? null,
  52. method: portal.demoSession?.kind === "wallet" ? "wallet" : "demo",
  53. signOut: async () => {
  54. portal.clearDemo();
  55. },
  56. };
  57. return <>{children(identity)}</>;
  58. }
  59. function SessionIdentityConsumer({
  60. children,
  61. }: {
  62. children: (identity: PortalIdentity) => ReactNode;
  63. }) {
  64. const portal = usePortalAuth();
  65. const [loading, setLoading] = useState(true);
  66. const [userId, setUserId] = useState<string | null>(null);
  67. const [email, setEmail] = useState<string | null>(null);
  68. const [method, setMethod] = useState<AuthMethod | null>(null);
  69. const [walletAddress, setWalletAddress] = useState<string | null>(null);
  70. const refresh = useCallback(async () => {
  71. const s = await fetchPortalSession();
  72. if (s.authenticated) {
  73. setUserId(s.userId);
  74. setEmail(s.email ?? null);
  75. setMethod(s.method ?? null);
  76. setWalletAddress(s.walletAddress ?? null);
  77. } else if (portal.demoSession) {
  78. setUserId(portal.demoSession.userId);
  79. setEmail(
  80. portal.demoSession.kind === "demo" ? portal.demoSession.label : null,
  81. );
  82. setMethod(
  83. portal.demoSession.kind === "wallet" ? "wallet" : "demo",
  84. );
  85. setWalletAddress(portal.demoSession.walletAddress ?? null);
  86. } else {
  87. setUserId(null);
  88. setEmail(null);
  89. setMethod(null);
  90. setWalletAddress(null);
  91. }
  92. setLoading(false);
  93. }, [portal.demoSession]);
  94. useEffect(() => {
  95. void refresh();
  96. const onFocus = () => void refresh();
  97. window.addEventListener("focus", onFocus);
  98. window.addEventListener("krypco-auth-change", onFocus);
  99. return () => {
  100. window.removeEventListener("focus", onFocus);
  101. window.removeEventListener("krypco-auth-change", onFocus);
  102. };
  103. }, [refresh]);
  104. const signedIn = Boolean(userId) || Boolean(portal.demoSession);
  105. const source: PortalIdentity["source"] = (() => {
  106. if (method === "wallet" || portal.demoSession?.kind === "wallet")
  107. return "wallet";
  108. if (method === "konnos") return "konnos";
  109. if (method === "demo" || portal.demoSession?.kind === "demo") return "demo";
  110. if (userId) return "briven";
  111. return null;
  112. })();
  113. const label =
  114. email ||
  115. (walletAddress
  116. ? `${walletAddress.slice(0, 6)}…${walletAddress.slice(-4)}`
  117. : null) ||
  118. portal.demoSession?.label ||
  119. userId;
  120. const identity: PortalIdentity = {
  121. isLoading: loading,
  122. isSignedIn: signedIn,
  123. label,
  124. source,
  125. userId: userId || portal.demoSession?.userId || null,
  126. walletAddress:
  127. walletAddress || portal.demoSession?.walletAddress || null,
  128. method:
  129. method ||
  130. (portal.demoSession?.kind === "wallet" ? "wallet" : null),
  131. signOut: async () => {
  132. try {
  133. await fetch("/api/auth/signout", {
  134. method: "POST",
  135. credentials: "include",
  136. headers: {
  137. "content-type": "application/json",
  138. rid: "session",
  139. },
  140. body: "{}",
  141. }).catch(() => null);
  142. await clearPortalSession();
  143. } finally {
  144. portal.clearDemo();
  145. setUserId(null);
  146. setEmail(null);
  147. setMethod(null);
  148. setWalletAddress(null);
  149. window.dispatchEvent(new Event("krypco-auth-change"));
  150. }
  151. },
  152. };
  153. return <>{children(identity)}</>;
  154. }