diff --git a/driver-pwa/frontend/src/App.tsx b/driver-pwa/frontend/src/App.tsx index d0b4dc0..55d0d3b 100644 --- a/driver-pwa/frontend/src/App.tsx +++ b/driver-pwa/frontend/src/App.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from "react"; import { Navigate, Route, Routes } from "react-router-dom"; import { useAuth } from "@/store/auth"; import { LoginPage } from "@/pages/LoginPage"; +import { TgWaitPage } from "@/pages/TgWaitPage"; import { OnboardingPage } from "@/pages/OnboardingPage"; import { BalancePage } from "@/pages/BalancePage"; import { TopupPage } from "@/pages/TopupPage"; @@ -18,6 +19,7 @@ export default function App() {
} /> + } /> } /> } /> } /> diff --git a/driver-pwa/frontend/src/pages/LoginPage.test.tsx b/driver-pwa/frontend/src/pages/LoginPage.test.tsx index f0d3434..3734520 100644 --- a/driver-pwa/frontend/src/pages/LoginPage.test.tsx +++ b/driver-pwa/frontend/src/pages/LoginPage.test.tsx @@ -6,6 +6,7 @@ import { MemoryRouter } from "react-router-dom"; vi.mock("@/api/driver", () => ({ authRequest: vi.fn(async () => ({ status: "code_sent", channel: "tg" })), authVerify: vi.fn(async () => ({ token: "T", driver_id: 7 })), + tgSession: vi.fn(async () => ({ pair_session: "sess123", deep_link: null })), })); import { LoginPage } from "./LoginPage"; @@ -19,6 +20,7 @@ describe("LoginPage", () => { it("requests code then verifies and stores session", async () => { wrap(); expect(screen.getByText(/Вход в приложение/i)).toBeInTheDocument(); + await userEvent.click(screen.getByRole("button", { name: /Войти по коду/i })); await userEvent.type(screen.getByPlaceholderText(/телефон/i), "9143054400"); await userEvent.click(screen.getByRole("button", { name: /Получить код/i })); expect(driver.authRequest).toHaveBeenCalledWith("79143054400"); diff --git a/driver-pwa/frontend/src/pages/LoginPage.tsx b/driver-pwa/frontend/src/pages/LoginPage.tsx index 7beb7c8..bb37eb2 100644 --- a/driver-pwa/frontend/src/pages/LoginPage.tsx +++ b/driver-pwa/frontend/src/pages/LoginPage.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { toast } from "sonner"; -import { authRequest, authVerify } from "@/api/driver"; +import { authRequest, authVerify, tgSession } from "@/api/driver"; import { digitsOnly, formatPhone } from "@/lib/format"; import { useAuth } from "@/store/auth"; import { Spinner } from "@/components/Spinner"; @@ -14,8 +14,22 @@ export function LoginPage() { const [step, setStep] = useState<"phone" | "code">("phone"); const [channel, setChannel] = useState(""); const [busy, setBusy] = useState(false); + const [mode, setMode] = useState<"choose" | "code">("choose"); const e164 = "7" + phone; + async function loginViaTelegram() { + setBusy(true); + try { + const r = await tgSession(); + sessionStorage.setItem("pp-pair-session", r.pair_session); + if (r.deep_link) { window.location.href = r.deep_link; } + nav("/tg-wait"); + } catch { + toast.error("Не удалось начать вход. Попробуйте «по коду»."); + setBusy(false); + } + } + async function requestCode() { if (phone.length < 10) return; setBusy(true); @@ -59,34 +73,46 @@ export function LoginPage() {

Премиум Водитель

Вход в приложение

- {step === "phone" ? ( + {mode === "choose" ? ( <> - setPhone(digitsOnly(e.target.value).replace(/^7/, "").slice(0, 10))} - className="card w-full px-4 py-3 mb-3 text-base bg-white" - /> - -

Код придёт в Telegram или MAX.

+

Telegram подтвердит ваш номер автоматически.

+ ) : ( <> -

Код отправлен в {channel === "max" ? "MAX" : "Telegram"}.

- setCode(digitsOnly(e.target.value).slice(0, 6))} - className="card w-full px-4 py-3 mb-3 text-base tracking-widest bg-white" - /> - - + {step === "phone" ? ( + <> + setPhone(digitsOnly(e.target.value).replace(/^7/, "").slice(0, 10))} + className="card w-full px-4 py-3 mb-3 text-base bg-white" + /> + +

Код придёт в Telegram или MAX.

+ + ) : ( + <> +

Код отправлен в {channel === "max" ? "MAX" : "Telegram"}.

+ setCode(digitsOnly(e.target.value).slice(0, 6))} + className="card w-full px-4 py-3 mb-3 text-base tracking-widest bg-white" + /> + + + + )} )}
diff --git a/driver-pwa/frontend/src/pages/TgWaitPage.tsx b/driver-pwa/frontend/src/pages/TgWaitPage.tsx new file mode 100644 index 0000000..a28e7cd --- /dev/null +++ b/driver-pwa/frontend/src/pages/TgWaitPage.tsx @@ -0,0 +1,23 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { usePairPoll } from "@/hooks/usePairPoll"; +import { Spinner } from "@/components/Spinner"; + +export function TgWaitPage() { + const nav = useNavigate(); + const pairSession = sessionStorage.getItem("pp-pair-session"); + const { status } = usePairPoll(pairSession); + + useEffect(() => { + if (status === "authenticated") { sessionStorage.removeItem("pp-pair-session"); nav("/", { replace: true }); } + if (status === "expired") { sessionStorage.removeItem("pp-pair-session"); nav("/login", { replace: true, state: { expired: true } }); } + }, [status, nav]); + + return ( +
+

Премиум Водитель

+

Подтвердите номер в Telegram — нажмите «Поделиться номером», затем вернитесь сюда.

+ +
+ ); +}