Skip to content

Commit

Permalink
Merge pull request #95 from Teri-anric/feat/add-telegram
Browse files Browse the repository at this point in the history
Prepare app to integrate telegram mini-app
  • Loading branch information
djeck1432 authored Oct 27, 2024
2 parents 5375f24 + e46ca20 commit 013d3ba
Show file tree
Hide file tree
Showing 26 changed files with 860 additions and 37 deletions.
3 changes: 3 additions & 0 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<!-- Font Awesome for Icons -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<link rel="icon" href="%PUBLIC_URL%/logo192.png" type="image/svg+xml">
<!-- Telegram mini-apps integration -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<script src="https://telegram.org/js/telegram-widget.js?22"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
68 changes: 46 additions & 22 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,44 @@ import SpotnetApp from './pages/spotnet/spotnet_app/SpotnetApp';
import Login from "./pages/Login";
import Form from "./pages/forms/Form";
import { connectWallet, logout } from './utils/wallet';
import { saveTelegramUser, getTelegramUserWalletId} from './utils/telegram';

function App() {
const [walletId, setWalletId] = useState(localStorage.getItem('wallet_id'));
const [tgUser, setTgUser] = useState(JSON.parse(localStorage.getItem('tg_user')));
const [error, setError] = useState(null);
const navigate = useNavigate();

useEffect(() => {
if (walletId) {
localStorage.setItem('wallet_id', walletId);
} else {
if (tgUser) {
saveTelegramUser(tgUser, walletId)
.then(() => console.log('Telegram user saved successfully'))
.catch(error => console.error('Error saving Telegram user:', error));
}
if (!walletId) {
localStorage.removeItem('wallet_id');
return;
}
localStorage.setItem('wallet_id', walletId);
}, [walletId]);

useEffect(() => {
if (!tgUser) {
localStorage.removeItem('tg_user');
return
}
if (!walletId) {
getTelegramUserWalletId(tgUser)
.then(fetchedWalletId => {
if (fetchedWalletId) {
setWalletId(fetchedWalletId);
}
})
.catch(error => console.error('Error fetching wallet ID:', error));
localStorage.setItem('tg_user', JSON.stringify(tgUser));
}
}, [tgUser, walletId])

const handleConnectWallet = async () => {
try {
setError(null);
Expand All @@ -41,25 +65,25 @@ function App() {
};

return (
<div className="App">
<Header walletId={walletId} onConnectWallet={handleConnectWallet} onLogout={handleLogout} />
<main>
{error && <div className="alert alert-danger">{error}</div>}
<Routes>
<Route
index
element={<SpotnetApp walletId={walletId} onConnectWallet={handleConnectWallet} onLogout={handleLogout} />}
/>
<Route
path="/login"
element={walletId ? <Navigate to="/" /> : <Login onConnectWallet={handleConnectWallet} />}
/>
<Route path="/dashboard" element={<Dashboard walletId={walletId} />} />
<Route path="/form" element={<Form walletId={walletId} setWalletId={setWalletId} />} />
</Routes>
</main>
<Footer />
</div>
<div className="App">
<Header tgUser={tgUser} setTgUser={setTgUser} walletId={walletId} onConnectWallet={handleConnectWallet} onLogout={handleLogout} />
<main>
{error && <div className="alert alert-danger">{error}</div>}
<Routes>
<Route
index
element={<SpotnetApp walletId={walletId} onConnectWallet={handleConnectWallet} onLogout={handleLogout} />}
/>
<Route
path="/login"
element={walletId ? <Navigate to="/" /> : <Login onConnectWallet={handleConnectWallet} />}
/>
<Route path="/dashboard" element={<Dashboard walletId={walletId} />} />
<Route path="/form" element={<Form walletId={walletId} setWalletId={setWalletId} />} />
</Routes>
</main>
<Footer />
</div>
);
}

Expand Down
51 changes: 51 additions & 0 deletions frontend/src/components/Telegram/TelegramLogin.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useState, useEffect } from 'react';
import './telegramLogin.css';

const TelegramLogin = ({ user, onLogin }) => {
useEffect(() => {
const initTelegramLogin = () => {
const tg = window.Telegram.WebApp;
tg.ready();

const user = tg.initDataUnsafe?.user;
if (user) {
onLogin(user);
}
};

initTelegramLogin();
}, [onLogin]);

const handleLogin = () => {
window.Telegram.Login.auth({
bot_id: process.env.REACT_APP_BOT_ID,
request_access: 'write'
}, onLogin)
};

const handleLogout = () => {
localStorage.removeItem('tg_user');
onLogin(null);
};

return (
<div className="telegram-login">
{user ? (
<div className="user-info" onClick={handleLogout}>
{user.photo_url ? (
<img src={user.photo_url} alt={user.first_name} className="user-photo" />
) : (
<div className="user-photo-placeholder" />
)}
<span className="user-name">{user.first_name}</span>
</div>
) : (
<button className="btn-telegram" onClick={handleLogin}>
Login with Telegram
</button>
)}
</div>
);
};

export default TelegramLogin;
61 changes: 61 additions & 0 deletions frontend/src/components/Telegram/telegramLogin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
.telegram-login {
display: flex;
align-items: center;
}

.user-info {
background: var(--gradient);
border-radius: 8px;
height: 52px;
width: 190px;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10px;
}

.user-photo {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}

.user-name {
color: var(--black);
font-size: 16px;
font-family: var(--text-font);
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.btn-telegram {
background: var(--button-gradient);
color: var(--black);
border: none;
height: 52px;
padding: 0 20px;
border-radius: 8px;
cursor: pointer;
font-size: 20px;
font-family: var(--text-font);
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}

.btn-telegram:hover {
background: var(--button-gradient-hover);
}

.btn-telegram:active {
background: var(--button-gradient-active);
}

/* div.user-photo-placeholder {
add style to user placeholder
} */
5 changes: 4 additions & 1 deletion frontend/src/components/header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React from 'react';
import './header.css'
import { ReactComponent as Logo } from "../../assets/images/logo.svg";
import { NavLink } from 'react-router-dom';
import TelegramLogin from '../Telegram/TelegramLogin';

function Header({ walletId, onConnectWallet, onLogout }) {
function Header({ walletId, onConnectWallet, onLogout, tgUser, setTgUser }) {
return (
<nav>
<div className='list-items'>
Expand All @@ -21,6 +22,7 @@ function Header({ walletId, onConnectWallet, onLogout }) {
</NavLink>
</div>
<div className='wallet-section'>
<TelegramLogin user={tgUser} onLogin={setTgUser} />
{walletId ? (
<div className='wallet-container'>
<button className='logout-button' onClick={onLogout}>
Expand All @@ -44,3 +46,4 @@ function Header({ walletId, onConnectWallet, onLogout }) {
}

export default Header;

2 changes: 2 additions & 0 deletions frontend/src/components/header/header.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ nav {
.wallet-section {
position: absolute;
right: 10px;
display: flex;
gap: 10px;
}

.nav-items a {
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/utils/telegram.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios from 'axios';

const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:8000';

export const saveTelegramUser = async (telegramUser, walletId) => {
try {
const response = await axios.post(`${API_BASE_URL}/api/telegram/save-user`, {
telegram_id: telegramUser.id,
username: telegramUser.username,
first_name: telegramUser.first_name,
last_name: telegramUser.last_name,
photo_url: telegramUser.photo_url,
wallet_id: walletId
});
return response.data;
} catch (error) {
console.error('Error saving Telegram user:', error);
throw error;
}
};

export const getTelegramUserWalletId = async (tg_user) => {
try {
const response = await axios.post(`${API_BASE_URL}/api/telegram/get-wallet-id/${tg_user.id}`, {
raw: window.Telegram.initData || tg_user,
is_webapp: !!window.Telegram.initData
});
return response.data.wallet_id;
} catch (error) {
console.error('Error getting wallet ID for Telegram user:', error);
throw error;
}
};
57 changes: 56 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ httpx = "0.27.2"
celery = "5.4.0"
redis = "5.2.0"
trio = "0.27.0"
aiogram = ">=3.13.1"

[tool.poetry.group.dev.dependencies]
black = "24.8.0"
Expand Down
22 changes: 22 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
fastapi==0.115.0
black==24.8.0
isort==5.13.2
psycopg2-binary==2.9.9
python-dotenv==1.0.1
uvicorn==0.30.6
starknet-py==0.24.2
jinja2==3.1.4
python-multipart==0.0.9
itsdangerous==2.2.0
alembic==1.13.3
psycopg2-binary==2.9.9
SQLAlchemy==2.0.35
pytest==8.3.3
pytest-asyncio==0.24.0
pytest-env==1.1.5
pytest-mock==3.14.0
httpx==0.27.2
aiogram>=3.13.1
celery==5.4.0
redis==5.2.0
trio==0.27.0
Loading

0 comments on commit 013d3ba

Please sign in to comment.