From b95877ac9ca8eac99a662fe1f7a09debeb3d9485 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sun, 1 Feb 2026 09:13:00 -0500 Subject: [PATCH] add ToS to website --- src/app/layout.tsx | 4 + src/app/settings/page.tsx | 166 +++++++++------------ src/app/terms/page.tsx | 302 ++++++++++++++++++++++++++++++++++++++ src/lib/api.ts | 37 ++++- 4 files changed, 415 insertions(+), 94 deletions(-) create mode 100644 src/app/terms/page.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5f0929c..847131f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -26,6 +26,10 @@ export default function RootLayout({ Privacy Policy • + + Terms of Service + + • Made & Hosted by{" "} (null); @@ -31,10 +31,9 @@ export default function SettingsPage() { // Telegram linking state const [linkStep, setLinkStep] = useState('idle'); - const [telegramUsername, setTelegramUsername] = useState(''); - const [verificationCode, setVerificationCode] = useState(null); - const [botLink, setBotLink] = useState(null); + const [deepLink, setDeepLink] = useState(null); const [linkError, setLinkError] = useState(null); + const [polling, setPolling] = useState(false); const token = typeof window !== 'undefined' ? localStorage.getItem('smirk_token') : null; @@ -81,25 +80,51 @@ export default function SettingsPage() { } }; - const handleStartLink = () => { - setLinkStep('entering'); + const handleStartLink = async () => { + if (!token) return; + setLinkStep('linking'); setLinkError(null); - }; - const handleSubmitUsername = async () => { - if (!token || !telegramUsername.trim()) return; - - setLinkError(null); try { - const result = await registerSocial(token, 'telegram', telegramUsername.trim()); - setVerificationCode(result.verification_code); - setBotLink(result.bot_link); - setLinkStep('verifying'); + const result = await initiateLink(token, 'telegram'); + setDeepLink(result.deep_link); + setLinkStep('waiting'); + // Start polling for verification + startPolling(); } catch (err) { - setLinkError(err instanceof Error ? err.message : 'Failed to start verification'); + setLinkError(err instanceof Error ? err.message : 'Failed to initiate link'); + setLinkStep('idle'); } }; + const startPolling = () => { + if (polling) return; + setPolling(true); + }; + + // Polling effect for verification status + useEffect(() => { + if (!polling || !token) return; + + const interval = setInterval(async () => { + try { + const socialsData = await getLinkedSocials(token); + setSocials(socialsData.socials); + const telegram = socialsData.socials.find((s) => s.platform === 'telegram'); + if (telegram?.verified) { + // Success! Stop polling and reset state + setPolling(false); + setLinkStep('idle'); + setDeepLink(null); + } + } catch { + // Ignore polling errors, keep trying + } + }, 3000); // Poll every 3 seconds + + return () => clearInterval(interval); + }, [polling, token]); + const handleUnlink = async (platform: string) => { if (!token) return; if (!confirm(`Unlink ${platform}?`)) return; @@ -114,27 +139,9 @@ export default function SettingsPage() { const handleCancelLink = () => { setLinkStep('idle'); - setTelegramUsername(''); - setVerificationCode(null); - setBotLink(null); + setDeepLink(null); setLinkError(null); - }; - - const handleVerificationDone = async () => { - if (!token) return; - // Refresh socials to check if verified - try { - const socialsData = await getLinkedSocials(token); - setSocials(socialsData.socials); - const telegram = socialsData.socials.find((s) => s.platform === 'telegram'); - if (telegram?.verified) { - handleCancelLink(); - } else { - setLinkError('Verification not complete. Send the code to the bot and try again.'); - } - } catch { - setLinkError('Failed to check verification status'); - } + setPolling(false); }; if (loading) { @@ -271,9 +278,11 @@ export default function SettingsPage() {

Telegram

{telegramSocial?.verified ? ( -

@{telegramSocial.username}

+

+ {telegramSocial.username ? `@${telegramSocial.username}` : telegramSocial.display_name || 'Linked'} +

) : telegramSocial?.pending_verification ? ( -

Pending: @{telegramSocial.username}

+

Verification pending

) : (

Not linked

)} @@ -298,68 +307,33 @@ export default function SettingsPage() {
{/* Link flow UI */} - {linkStep === 'entering' && ( + {linkStep === 'linking' && (
- -
- setTelegramUsername(e.target.value)} - placeholder="@username" - className="flex-1 px-3 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:border-[#fbeb0a]" - /> - - +
+
+ Setting up link...
- {linkError &&

{linkError}

}
)} - {linkStep === 'verifying' && verificationCode && ( + {linkStep === 'waiting' && deepLink && (
-
-

Your verification code:

-

{verificationCode}

-
+

+ Click the button below to open Telegram and link your account automatically: +

-
    -
  1. - 1. Open{' '} - - @smirk_wallet_bot - {' '} - on Telegram -
  2. -
  3. 2. Send: /verify {verificationCode}
  4. -
  5. 3. Click "I've verified" below
  6. -
- - + +
+
+ Waiting for verification... +
+ {linkError &&

{linkError}

}
)} diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx new file mode 100644 index 0000000..5f1935c --- /dev/null +++ b/src/app/terms/page.tsx @@ -0,0 +1,302 @@ +'use client'; + +import Image from 'next/image'; +import Link from 'next/link'; + +export default function Terms() { + return ( +
+ {/* Header */} +
+ + Smirk + +

Terms of Service

+
+ + {/* Content */} +
+

+ Last Updated: January 31, 2026 +

+ +

+ BY ACCESSING OR USING SMIRK WALLET, YOU AGREE TO BE BOUND BY THESE TERMS OF SERVICE. + IF YOU DO NOT AGREE, DO NOT USE OUR SERVICES. +

+ + {/* Section 1 */} +
+

1. Eligibility

+

+ You represent and warrant that you: +

+
    +
  • Are of legal age to form a binding contract in your jurisdiction
  • +
  • Have not previously been suspended or removed from using our Services
  • +
  • Have full power and authority to enter into this agreement
  • +
  • Will not use these services in violation of any applicable laws
  • +
+
+ + {/* Section 2 */} +
+

2. Account & Wallet Security

+

+ When you create a Smirk wallet, you agree to: +

+
    +
  • + Secure Your Recovery Phrase: You are solely + responsible for backing up and protecting your 24-word recovery phrase (mnemonic). + This phrase is the only way to recover your wallet. +
  • +
  • + Password Protection: Maintain the security + of your wallet password and restrict access to your devices. +
  • +
  • + Promptly Report Issues: Notify us if you + discover or suspect any security breaches related to your account. +
  • +
  • + Accept Responsibility: Take responsibility + for all activities that occur under your wallet, including any authorized or + unauthorized access. +
  • +
+
+ + {/* Section 3 */} +
+

3. Description of Services

+ +

3.1 Non-Custodial Wallet

+

+ Smirk is a non-custodial multi-cryptocurrency + wallet supporting Bitcoin (BTC), Litecoin (LTC), Monero (XMR), Wownero (WOW), and + Grin (GRIN). Your private spend keys are generated and stored locally on your device. + They are never transmitted to our servers. We cannot access, freeze, or seize your funds. +

+ +

3.2 Social Tipping

+

+ Smirk enables you to send cryptocurrency tips to users on social platforms (Telegram, + Discord, etc.) and via public tip links. Tips are encrypted and can only be claimed + by the intended recipient (or anyone with the link for public tips). +

+ +

3.3 No Recovery Phrase Retrieval

+

+ Our Services do not store your recovery phrase or unencrypted private keys. Therefore, + we cannot assist you with recovery phrase retrieval. +

+
+ WARNING: IF YOU LOSE YOUR RECOVERY PHRASE, + YOU WILL PERMANENTLY LOSE ACCESS TO YOUR FUNDS. THERE IS NO WAY TO RECOVER THEM. +
+ +

3.4 Transaction Processing

+

+ All cryptocurrency transactions are processed by their respective decentralized + networks (Bitcoin, Litecoin, Monero, Wownero, Grin). We do not control these networks + and cannot guarantee transaction speed, confirmation, or reversal. Once a transaction + is broadcast, it cannot be cancelled or modified. +

+
+ + {/* Section 4 */} +
+

4. Fees

+
    +
  • + Service Fees: Access to Smirk wallet is + currently free. We may introduce fees for certain services in the future. +
  • +
  • + Network Fees: All cryptocurrency transactions + require network fees (miner fees) paid to the respective blockchain network. These + fees are not collected by Smirk and are displayed before you confirm any transaction. +
  • +
+
+ + {/* Section 5 */} +
+

5. Acceptable Use

+

You agree that you will NOT:

+
    +
  • Use our Services to engage in illegal activities including fraud, money laundering, + or terrorist financing
  • +
  • Attempt to interfere with, disrupt, or overburden our Services
  • +
  • Use automated means (bots, scrapers) to access our Services without permission
  • +
  • Attempt to circumvent any security measures or access controls
  • +
  • Introduce viruses, malware, or other harmful code
  • +
  • Impersonate others or provide false information
  • +
  • Use our Services to harass, abuse, or harm others
  • +
+
+ + {/* Section 6 */} +
+

6. Taxes

+

+ It is your sole responsibility to determine what taxes apply to your cryptocurrency + transactions and to report and remit the correct tax to the appropriate tax authority. + Smirk is not responsible for determining, collecting, reporting, or remitting any + taxes arising from your use of our Services. +

+
+ + {/* Section 7 */} +
+

7. Assumption of Risk

+

+ You acknowledge and agree that there are risks associated with using cryptocurrency + and Internet-based services, including but not limited to: +

+
    +
  • Price volatility and potential loss of value
  • +
  • Risk of hardware, software, and Internet connection failures
  • +
  • Risk of malicious software or phishing attacks
  • +
  • Risk of unauthorized access to your devices or accounts
  • +
  • Irreversibility of cryptocurrency transactions
  • +
  • Regulatory changes that may affect cryptocurrency use
  • +
+

+ Smirk is not responsible for any losses arising from forgotten recovery phrases, + incorrect addresses, server failures, phishing attacks, or any other unauthorized + third-party activities. +

+
+ + {/* Section 8 */} +
+

8. Discontinuance of Services

+

+ We may, in our sole discretion and without liability to you, modify or discontinue + any portion of our Services at any time. You are solely responsible for maintaining + a backup of your recovery phrase outside of the Services. If you have your recovery + phrase, you can always access your funds using any compatible wallet software, even + if Smirk discontinues its Services. +

+
+ + {/* Section 9 */} +
+

9. Disclaimer of Warranties

+

+ OUR SERVICES ARE PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS. + TO THE MAXIMUM EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, + INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND + NON-INFRINGEMENT. WE DO NOT WARRANT THAT OUR SERVICES WILL BE UNINTERRUPTED, + ERROR-FREE, OR SECURE. +

+
+ + {/* Section 10 */} +
+

10. Limitation of Liability

+

+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, SMIRK AND ITS DIRECTORS, EMPLOYEES, AND + AGENTS SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, + OR PUNITIVE DAMAGES, INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA, OR FUNDS, + ARISING FROM YOUR USE OF OR INABILITY TO USE OUR SERVICES. +

+

+ IN NO EVENT SHALL OUR AGGREGATE LIABILITY EXCEED ONE HUNDRED DOLLARS ($100 USD). +

+
+ + {/* Section 11 */} +
+

11. Indemnification

+

+ You agree to defend, indemnify, and hold harmless Smirk and its officers, directors, + employees, and agents from any claims, damages, losses, or expenses arising from: + (a) your use of our Services; (b) your violation of these Terms; (c) your violation + of any rights of any third party; or (d) any content you submit through our Services. +

+
+ + {/* Section 12 */} +
+

12. Privacy

+

+ Please refer to our{' '} + + Privacy Policy + + {' '}for information about how we collect, use, and protect your information. +

+
+ + {/* Section 13 */} +
+

13. Modifications to Terms

+

+ We reserve the right to modify these Terms at any time. We will provide notice of + material changes by updating the "Last Updated" date at the top of these Terms. + Your continued use of our Services after any changes constitutes your acceptance of + the new Terms. +

+
+ + {/* Section 14 */} +
+

14. Governing Law

+

+ These Terms shall be governed by and construed in accordance with the laws of the + jurisdiction where Smirk operates, without regard to conflict of law principles. +

+
+ + {/* Section 15 */} +
+

15. Severability

+

+ If any provision of these Terms is found to be invalid or unenforceable, the remaining + provisions shall remain in full force and effect. +

+
+ + {/* Quick Summary */} +
+

Quick Summary

+
    +
  • Non-Custodial: You control your keys. We cannot access or recover your funds.
  • +
  • Your Responsibility: Back up your recovery phrase. Losing it means losing your funds forever.
  • +
  • Irreversible: Cryptocurrency transactions cannot be cancelled or reversed.
  • +
  • No Guarantees: We provide the service as-is. Use at your own risk.
  • +
  • Be Legal: Don't use Smirk for illegal activities.
  • +
+
+ + {/* Contact */} +
+

Questions?

+

+ If you have questions about these Terms of Service, please contact us through our + official channels or at the support email provided in the extension. +

+
+ + {/* Back link */} +
+ + ← Back to Smirk + +
+
+
+ ); +} diff --git a/src/lib/api.ts b/src/lib/api.ts index 955e9a9..7c9ef9d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -87,12 +87,20 @@ export interface RegisterSocialResponse { export interface LinkedSocial { platform: string; - username: string; + username: string | null; + display_name: string | null; + platform_user_id: string | null; verified: boolean; verified_at: string | null; pending_verification: boolean; } +export interface InitiateLinkResponse { + deep_link: string; + code: string; + expires_at: string; +} + export interface LinkedSocialsResponse { socials: LinkedSocial[]; } @@ -119,6 +127,33 @@ export async function registerSocial( return res.json(); } +/** + * Initiate social link via deep link flow. + * + * Returns a deep link that opens the bot. User clicks the link, + * bot extracts their info automatically, no manual username entry. + */ +export async function initiateLink( + token: string, + platform: string +): Promise { + const res = await fetch(`${API_URL}/api/v1/socials/initiate-link`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ platform }), + }); + + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.error || 'Failed to initiate link'); + } + + return res.json(); +} + export async function getLinkedSocials(token: string): Promise { const res = await fetch(`${API_URL}/api/v1/socials/me`, { headers: { Authorization: `Bearer ${token}` },