<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: byArto</title>
    <description>The latest articles on Forem by byArto (@byartocrypto).</description>
    <link>https://forem.com/byartocrypto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3808048%2Fbfc97701-7690-4e86-aa43-1e34eaaabc43.jpg</url>
      <title>Forem: byArto</title>
      <link>https://forem.com/byartocrypto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/byartocrypto"/>
    <language>en</language>
    <item>
      <title>I Built a Subscription Tracker That Works as Both a PWA and a Telegram Mini App</title>
      <dc:creator>byArto</dc:creator>
      <pubDate>Fri, 06 Mar 2026 13:21:31 +0000</pubDate>
      <link>https://forem.com/byartocrypto/i-built-a-subscription-tracker-that-works-as-both-a-pwa-and-a-telegram-mini-app-6i2</link>
      <guid>https://forem.com/byartocrypto/i-built-a-subscription-tracker-that-works-as-both-a-pwa-and-a-telegram-mini-app-6i2</guid>
      <description>&lt;p&gt;A few months ago I had a simple problem: too many subscriptions, zero visibility into what I was actually paying. The standard fixes (spreadsheet, notes app) never stuck.&lt;/p&gt;

&lt;p&gt;So I built Subeasy. A subscription tracker that lives inside Telegram and also works as a standalone PWA. Same codebase, two platforms&lt;/p&gt;

&lt;p&gt;Here's what the build actually looked like&lt;/p&gt;

&lt;p&gt;The stack&lt;/p&gt;

&lt;p&gt;• Next.js 16 (App Router)&lt;br&gt;
• React 19 + TypeScript 5&lt;br&gt;
• Tailwind CSS 4&lt;br&gt;
• Framer Motion for animations&lt;br&gt;
• Supabase (PostgreSQL) for cloud sync&lt;br&gt;
• Recharts for analytics&lt;br&gt;
• Vercel for deploys&lt;/p&gt;

&lt;p&gt;Nothing exotic. The interesting part isn't the stack, it's the decisions around data and platform&lt;/p&gt;

&lt;p&gt;Offline-first, always&lt;/p&gt;

&lt;p&gt;The biggest design decision early on: localStorage as the source of truth, Supabase as the sync layer.&lt;/p&gt;

&lt;p&gt;Every subscription lives in localStorage first. The app fully works without a network connection or even an account. When the user logs in, it syncs to Supabase.&lt;/p&gt;

&lt;p&gt;The sync strategy: full pull from remote, merge (remote wins on conflicts), full push. Debounced at 1 second&lt;/p&gt;

&lt;p&gt;Why? Most apps require signup before you can do anything. That kills conversion. With offline-first, the user adds 5 subscriptions and sees their monthly total before touching an account screen&lt;/p&gt;

&lt;p&gt;Dual platform from one codebase&lt;/p&gt;

&lt;p&gt;The TelegramProvider wraps the entire app:&lt;/p&gt;

&lt;p&gt;const tg = window.Telegram?.WebApp&lt;br&gt;
if (tg) {&lt;br&gt;
tg.ready()&lt;br&gt;
tg.expand() // Fullscreen mode&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;HapticFeedback on every interaction — taps, submissions, errors. That tactile feedback makes it feel native rather than a webpage in a webview.&lt;/p&gt;




&lt;p&gt;BackButton - the detail that matters most&lt;/p&gt;

&lt;p&gt;Telegram's back button needs to close modals in the right order. If a user has a detail screen open and taps back, it should close that modal, not exit the app.&lt;/p&gt;

&lt;p&gt;useEffect(() =&amp;gt; {&lt;br&gt;
const hasOpenModal = showAddModal  selectedSubId  editingSubId  showSearch  showNotifications&lt;br&gt;
if (hasOpenModal) {&lt;br&gt;
tg.BackButton.show()&lt;br&gt;
tg.BackButton.onClick(handleBackButton)&lt;br&gt;
} else {&lt;br&gt;
tg.BackButton.hide()&lt;br&gt;
}&lt;br&gt;
}, [showAddModal, selectedSubId, ...])&lt;/p&gt;

&lt;p&gt;Getting this wrong feels broken. Getting it right feels invisible — which is exactly what you want&lt;/p&gt;

&lt;p&gt;otifications without a bot&lt;/p&gt;

&lt;p&gt;Telegram Mini Apps can't push notifications the way a regular bot can. So notifications run through Service Worker + Web Notifications API.&lt;/p&gt;

&lt;p&gt;Works well on Android. On iOS we fall back to an in-app notification panel. The road forward is Telegram's native notification features - they keep expanding what Mini Apps can do.&lt;/p&gt;




&lt;p&gt;Exchange rates via Edge Function&lt;/p&gt;

&lt;p&gt;export const runtime = 'edge'&lt;/p&gt;

&lt;p&gt;export async function GET() {&lt;br&gt;
const res = await fetch('&lt;a href="https://www.cbr.ru/scripts/XML_daily.asp'" rel="noopener noreferrer"&gt;https://www.cbr.ru/scripts/XML_daily.asp'&lt;/a&gt;)&lt;br&gt;
return Response.json({ rate, updatedAt: new Date() })&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Cached in localStorage for 1 hour. Currency toggle is instant, no loading spinner.&lt;/p&gt;




&lt;p&gt;The Sub Score&lt;/p&gt;

&lt;p&gt;A 0-100 rating of subscription health:&lt;/p&gt;

&lt;p&gt;• Budget (25 pts) - % of income on subscriptions&lt;br&gt;
• Activity (25 pts) - active vs inactive ratio&lt;br&gt;
• Duplicates (20 pts) - same category, multiple services&lt;br&gt;
• Diversification (20 pts) - one category eating 80%+ of budget&lt;br&gt;
• Trials (5 pts) - unconverted trials about to charge&lt;br&gt;
• Annual plans (5 pts) - using annual pricing where it saves money&lt;/p&gt;

&lt;p&gt;It doesn't tell users what to do. It makes the situation visible. People see a C and want to fix it. That's enough&lt;/p&gt;




&lt;p&gt;i18n at 380+ keys&lt;/p&gt;

&lt;p&gt;Full Russian and English. Custom useLanguage() hook, t('key') function.&lt;/p&gt;

&lt;p&gt;Main lesson: build the translation structure before writing any UI copy. Retrofitting i18n into existing components is painful.&lt;/p&gt;




&lt;p&gt;What's next (PRO tier)&lt;/p&gt;

&lt;p&gt;Free version stays fully functional with no subscription limits - deliberate product decision.&lt;/p&gt;

&lt;p&gt;PRO will add:&lt;/p&gt;

&lt;p&gt;• Themes and accent colors&lt;br&gt;
• Multi-currency (EUR, GBP, TRY, KZT, AMD)&lt;br&gt;
• Price history tracking&lt;br&gt;
• PDF/CSV export&lt;br&gt;
• AI insights ("you're spending 3x the average on streaming")&lt;br&gt;
• Family plan (shared workspace, 2-6 people)&lt;br&gt;
• Payment via Telegram Stars - no Stripe, no acquiring needed&lt;/p&gt;

&lt;p&gt;That last one is a natural fit for a Mini App already inside Telegram.&lt;/p&gt;




&lt;p&gt;Try it&lt;/p&gt;

&lt;p&gt;App: t.me/Subeasyapp_bot/subeasy&lt;br&gt;
Web: &lt;a href="http://www.subeasy.org" rel="noopener noreferrer"&gt;www.subeasy.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Free, no bank connections. You add subscriptions manually. Turns out people prefer it that way&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
