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"
- />
-
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 — нажмите «Поделиться номером», затем вернитесь сюда.
+
+
+ );
+}