Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion frontend/codegen.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { readdir, readFile, writeFile } from 'fs/promises';
import { join } from 'path';

const __dirname = dirname(fileURLToPath(import.meta.url));

const builder = new TSBuilder({
Expand Down
1 change: 0 additions & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Telegram Payments</title>
<!-- Telegram Web App SDK - MUST be loaded before your app -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
Expand Down
6,875 changes: 5,272 additions & 1,603 deletions frontend/package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@
},
"dependencies": {
"@cosmjs/amino": "^0.36.2",
"@cosmjs/cosmwasm-stargate": "^0.37.0",
"@cosmjs/crypto": "^0.36.2",
"@cosmjs/encoding": "^0.36.2",
"@cosmjs/proto-signing": "^0.36.2",
"@cosmjs/stargate": "^0.37.0",
"@keplr-wallet/types": "^0.12.282",
"@twa-dev/sdk": "^8.0.2",
"@walletconnect/sign-client": "^2.22.4",
"@walletconnect/types": "^2.22.4",
"@walletconnect/utils": "^2.22.4",
"buffer": "^6.0.3",
"cosmjs-types": "^0.10.1",
"react": "^19.1.1",
"react-dom": "^19.1.1"
"react-dom": "^19.1.1",
"react-router-dom": "^7.9.5"
},
"devDependencies": {
"@cosmwasm/ts-codegen": "^1.13.3",
Expand Down
129 changes: 75 additions & 54 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,94 @@
import { useState, useEffect } from 'react'
import WebApp from '@twa-dev/sdk'
import { AccountCreation } from './components/AccountCreation'
import { Dashboard } from './components/Dashboard'
import { WalletConnect } from './pages/WalletConnect'
import { hasAccount } from './services/storage'
import './App.css'
import { useEffect, useState } from "react";
import WebApp from "@twa-dev/sdk";
import { Onboarding } from "./components/Onboarding";
import { RegisteredDashboard } from "./components/RegisteredDashboard";
import { useSigningClient } from "./hooks/useSigningClient";
import "./App.css";

function App() {
const [accountExists, setAccountExists] = useState<boolean>(false)
const [isReady, setIsReady] = useState(false)
const [currentPath, setCurrentPath] = useState(window.location.pathname)
// Track registration completion in localStorage
const REGISTRATION_KEY = "telegram_payments_registered";

useEffect(() => {
// Initialize Telegram Web App
WebApp.ready()
WebApp.expand()
function isUserRegistered(): boolean {
return localStorage.getItem(REGISTRATION_KEY) === "true";
}

// Set header color to match theme
WebApp.setHeaderColor('bg_color')
function setUserRegistered(registered: boolean) {
if (registered) {
localStorage.setItem(REGISTRATION_KEY, "true");
} else {
localStorage.removeItem(REGISTRATION_KEY);
}
}

// Check if account exists
setAccountExists(hasAccount())
setIsReady(true)
function App() {
const [isRegistered, setIsRegistered] = useState(false);
const [isLoading, setIsLoading] = useState(true);

// Log Telegram user info (if available)
if (WebApp.initDataUnsafe.user) {
console.log('Telegram User:', WebApp.initDataUnsafe.user)
}
// Check wallet connection status
const { client: signingClient, isReady: signerReady } = useSigningClient();

// Listen for path changes
const handlePathChange = () => {
setCurrentPath(window.location.pathname)
}
window.addEventListener('popstate', handlePathChange)
return () => window.removeEventListener('popstate', handlePathChange)
}, [])
useEffect(() => {
// Initialize Telegram WebApp
WebApp.ready();
WebApp.expand();

const handleAccountCreated = () => {
setAccountExists(true)
}
// Apply Telegram theme
document.documentElement.style.setProperty(
"--tg-theme-bg-color",
WebApp.backgroundColor
);
document.documentElement.style.setProperty(
"--tg-theme-text-color",
WebApp.themeParams.text_color || "#000000"
);
document.documentElement.style.setProperty(
"--tg-theme-hint-color",
WebApp.themeParams.hint_color || "#999999"
);
document.documentElement.style.setProperty(
"--tg-theme-button-color",
WebApp.themeParams.button_color || "#3390ec"
);
document.documentElement.style.setProperty(
"--tg-theme-button-text-color",
WebApp.themeParams.button_text_color || "#ffffff"
);
document.documentElement.style.setProperty(
"--tg-theme-secondary-bg-color",
WebApp.themeParams.secondary_bg_color || "#f0f0f0"
);

// Check registration status
setIsRegistered(isUserRegistered());
setIsLoading(false);
}, []);

const handleOnboardingComplete = () => {
setUserRegistered(true);
setIsRegistered(true);
};

const handleLogout = () => {
setAccountExists(false)
}
setIsRegistered(false);
setUserRegistered(false);
};

if (!isReady) {
if (isLoading || !signerReady) {
return (
<div className="loading">
<div className="spinner"></div>
<div className="loading-screen">
<div className="loading-spinner">💸</div>
<p>Loading...</p>
</div>
)
);
}

// Route for wallet connection redirect
if (currentPath === '/connect') {
return <WalletConnect />
// Step 1: Not registered or wallet not connected - show onboarding (includes account creation)
if (!isRegistered || !signingClient) {
return <Onboarding onComplete={handleOnboardingComplete} />;
}

// Main app routes
return (
<div className="app">
{accountExists ? (
<Dashboard onLogout={handleLogout} />
) : (
<AccountCreation onAccountCreated={handleAccountCreated} />
)}
</div>
)
// Step 2: Registered user with connected wallet - show main dashboard
return <RegisteredDashboard onLogout={handleLogout} />;
}

export default App
export default App;
46 changes: 35 additions & 11 deletions frontend/src/components/AccountCreation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
import WebApp from "@twa-dev/sdk";
import { generateKeyPair, importKeyPair } from "../services/crypto";
import type { KeyPair } from "../services/crypto";
import { saveAccount } from "../services/storage";
import { saveAccount, saveMnemonic, saveWalletType } from "../services/storage";
import {
showAlert,
showConfirm,
Expand Down Expand Up @@ -171,6 +171,7 @@ export function AccountCreation({ onAccountCreated }: AccountCreationProps) {

hapticNotification("success");
await new Promise((resolve) => setTimeout(resolve, 500));
saveWalletType('walletconnect');
saveAccount({
address: account.address,
publicKey: account.publicKey,
Expand Down Expand Up @@ -269,6 +270,7 @@ export function AccountCreation({ onAccountCreated }: AccountCreationProps) {
localStorage.removeItem("wc_connecting_state");
hapticNotification("success");
await new Promise((resolve) => setTimeout(resolve, 500));
saveWalletType('walletconnect');
saveAccount({
address: account.address,
publicKey: account.publicKey,
Expand Down Expand Up @@ -336,12 +338,23 @@ export function AccountCreation({ onAccountCreated }: AccountCreationProps) {
hapticImpact("medium");
try {
const importedKeyPair = await importKeyPair(trimmedMnemonic);
hapticNotification("success");
saveAccount({
address: importedKeyPair.address,
publicKey: importedKeyPair.publicKey,
});
onAccountCreated();

showConfirm(
"Save mnemonic to device for easy signing? (Less secure but more convenient)\n\nIf NO, you'll need to enter it each time you sign.",
(saveMnemonicToDevice) => {
if (saveMnemonicToDevice) {
// Save mnemonic to localStorage
saveMnemonic(trimmedMnemonic);
}
saveWalletType('local');
hapticNotification("success");
saveAccount({
address: importedKeyPair.address,
publicKey: importedKeyPair.publicKey,
});
onAccountCreated();
}
);
} catch (err) {
console.error("Failed to import mnemonic:", err);
hapticNotification("error");
Expand Down Expand Up @@ -379,6 +392,7 @@ export function AccountCreation({ onAccountCreated }: AccountCreationProps) {
console.log("✅ Wallet approved! Account:", walletAccount);

hapticNotification("success");
saveWalletType('walletconnect');
saveAccount({
address: walletAccount.address,
publicKey: walletAccount.publicKey,
Expand All @@ -404,17 +418,27 @@ export function AccountCreation({ onAccountCreated }: AccountCreationProps) {
if (!keyPair) return;

showConfirm(
"Have you saved your mnemonic? It will not be shown again!",
(confirmed) => {
if (confirmed) {
"Save mnemonic to device for easy signing? (Less secure but more convenient)\n\nIf NO, you'll need to enter it each time you sign.",
(saveMnemonicToDevice) => {
if (saveMnemonicToDevice) {
// Save mnemonic to localStorage
saveMnemonic(keyPair.mnemonic);
saveWalletType('local');
hapticNotification("success");
saveAccount({
address: keyPair.address,
publicKey: keyPair.publicKey,
});
onAccountCreated();
} else {
hapticNotification("warning");
// Don't save mnemonic, only save address and public key
saveWalletType('local');
hapticNotification("success");
saveAccount({
address: keyPair.address,
publicKey: keyPair.publicKey,
});
onAccountCreated();
}
}
);
Expand Down
Loading