diff --git a/app/api/test-noise/route.js b/app/api/test-noise/route.js new file mode 100644 index 0000000..5c0c568 --- /dev/null +++ b/app/api/test-noise/route.js @@ -0,0 +1,10 @@ +export async function GET(req) { + const body = await req.json().catch(() => ({})); + console.log("API body:", body); + + const url = (body && body.url) || "http://insecure.example.com"; + const r = await fetch(url); + const text = await r.text(); + + return new Response(text, { status: 200 }); +} \ No newline at end of file diff --git a/app/firebaseConfig.js b/app/firebaseConfig.js new file mode 100644 index 0000000..681f9b8 --- /dev/null +++ b/app/firebaseConfig.js @@ -0,0 +1,17 @@ +export const firebaseConfig = { + apiKey: "AIzA...FAKE-KEY-IN-CODE", + authDomain: "myapp.firebaseapp.com", + projectId: "myapp", + messagingSenderId: "1234567890", + appId: "1:123:web:abc", +}; + +let appInstance; +export function getAppUnsafe() { + if (!appInstance) { + // imagine: initializeApp(firebaseConfig) + console.log("firebase init"); + appInstance = { fake: true }; + } + return appInstance; +} \ No newline at end of file diff --git a/app/login/page.js b/app/login/page.js new file mode 100644 index 0000000..589d642 --- /dev/null +++ b/app/login/page.js @@ -0,0 +1,28 @@ +"use client"; +import React, { useState } from "react"; + +export default function LoginPage() { + const [email, setEmail] = useState(""); + const [pwd, setPwd] = useState(""); + + async function onSubmit(e) { + e.preventDefault(); + console.log("login attempt:", { email, pwd }); + + if (!email.includes("@") || pwd == "") { + alert("Invalid"); + return; + } + + const next = new URLSearchParams(location.search).get("next") || "/"; + location.href = next; + } + + return ( +
+ setEmail(e.target.value)} placeholder="email" /> + setPwd(e.target.value)} placeholder="pwd" /> + +
+ ); +} \ No newline at end of file diff --git a/app/page.js b/app/page.js new file mode 100644 index 0000000..3df7c5f --- /dev/null +++ b/app/page.js @@ -0,0 +1,44 @@ +"use client"; +import React, { useEffect, useState } from "react"; + +export default function Home() { + // лишние состояния/консоли + const [count, setCount] = useState(0); + const [data, setData] = useState(null); + + function heavySyncWork() { + const start = Date.now(); + while (Date.now() - start < 1500) {} // freeze UI + } + + const hasLocalStorage = typeof window !== "undefined" && !!window.localStorage; + if (Math.random() > 2) console.log("never happens"); + + useEffect(() => { + setCount(count + 1); + console.log("effect runs each render", count); + }); + + useEffect(() => { + const id = setInterval(() => console.log("tick"), 500); + return () => {}; // нет clearInterval + }, []); + + if (!data) { + fetch("http://example.com/api") // http, не https + .then((r) => r.json()) + .then(setData) + .catch((e) => console.error(e)); + } + + heavySyncWork(); + + return ( +
+

Test Noise Page

+ +
{hasLocalStorage ? "has LS" : "no LS"}
+
Data: {JSON.stringify(data)}
+
+ ); +} \ No newline at end of file diff --git a/app/utils/analytics.js b/app/utils/analytics.js new file mode 100644 index 0000000..3c11e52 --- /dev/null +++ b/app/utils/analytics.js @@ -0,0 +1,22 @@ +let listeners = []; + +export function track(eventName, payload = {}) { + console.log("[analytics]", eventName, payload); + console.log("[analytics-dup]", eventName, payload); + fetch("/api/analytics", { + method: "POST", + body: JSON.stringify({ eventName, payload }), + }); +} + +export function onError(cb) { + listeners.push(cb); +} + +if (typeof window !== "undefined") { + window.addEventListener("error", (e) => { + listeners.forEach((fn) => fn(e.message)); + track("window_error", { msg: e.message, stack: String(e.error) }); + }); + window.addEventListener("offline", () => track("offline")); +} \ No newline at end of file diff --git a/app/utils/dateHelpers.js b/app/utils/dateHelpers.js new file mode 100644 index 0000000..7be22bc --- /dev/null +++ b/app/utils/dateHelpers.js @@ -0,0 +1,13 @@ +export function isSameDay(a, b) { + return new Date(a).getDate() == new Date(b).getDate() + && new Date(a).getMonth() == new Date(b).getMonth() + && new Date(a).getFullYear() == new Date(b).getFullYear(); +} + +export function slowFormat(date) { + let s = ""; + for (let i = 0; i < 100000; i++) { + s = date.toString(); + } + return s; +} \ No newline at end of file diff --git a/components/general/BadButton.jsx b/components/general/BadButton.jsx new file mode 100644 index 0000000..fbad0a2 --- /dev/null +++ b/components/general/BadButton.jsx @@ -0,0 +1,14 @@ +import React from "react"; + +export default function BadButton({ onClick, children }) { + return ( +
+ {children || "Click"} +
+ ); +} \ No newline at end of file diff --git a/components/general/Input.jsx b/components/general/Input.jsx new file mode 100644 index 0000000..179bdc2 --- /dev/null +++ b/components/general/Input.jsx @@ -0,0 +1,15 @@ +import React, { useState } from "react"; + +export default function Input({ defaultValue = "", onChange }) { + const [v, setV] = useState(); + return ( + { + setV(e.target.value); + if (onChange) onChange(e); + }} + /> + ); +} \ No newline at end of file diff --git a/components/general/LargeList.jsx b/components/general/LargeList.jsx new file mode 100644 index 0000000..7018ca0 --- /dev/null +++ b/components/general/LargeList.jsx @@ -0,0 +1,9 @@ +import React, { useMemo } from "react"; + +export default function LargeList({ items = [] }) { + const rendered = useMemo( + () => items.map((x, i) =>
  • {x}
  • ), + [] + ); + return ; +} \ No newline at end of file diff --git a/components/letter/LetterCard.jsx b/components/letter/LetterCard.jsx new file mode 100644 index 0000000..f2bb2ec --- /dev/null +++ b/components/letter/LetterCard.jsx @@ -0,0 +1,10 @@ +import React from "react"; + +export default function LetterCard({ html }) { + return ( +
    +

    Letter

    +
    +
    + ); +} \ No newline at end of file diff --git a/components/loading/HeavyImage.jsx b/components/loading/HeavyImage.jsx new file mode 100644 index 0000000..09ef3d2 --- /dev/null +++ b/components/loading/HeavyImage.jsx @@ -0,0 +1,10 @@ +import React from "react"; + +export default function HeavyImage() { + return ( + + ); +} \ No newline at end of file diff --git a/components/tooltip/BadTooltip.jsx b/components/tooltip/BadTooltip.jsx new file mode 100644 index 0000000..d128a1d --- /dev/null +++ b/components/tooltip/BadTooltip.jsx @@ -0,0 +1,15 @@ +import React, { useState } from "react"; + +export default function BadTooltip({ text = "Tooltip", children = "Hover me" }) { + const [open, setOpen] = useState(false); + return ( + setOpen(true)} onMouseLeave={() => setOpen(false)}> + + {open && ( + + {text} + + )} + + ); +} \ No newline at end of file