File size: 10,378 Bytes
fcf8749
 
 
3b27dbe
 
fcf8749
 
 
3b27dbe
 
 
 
 
 
 
 
fcf8749
 
 
 
 
 
 
3b27dbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fcf8749
3b27dbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fcf8749
3b27dbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fcf8749
3b27dbe
 
 
 
 
 
 
fcf8749
3b27dbe
fcf8749
3b27dbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fcf8749
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import { useState, useEffect } from 'react'
import './Navbar.css'

const API_URL = import.meta.env.VITE_API_URL || 'https://fairrelay-backend.onrender.com'

export default function Navbar() {
  const [scrolled, setScrolled] = useState(false)
  const [menuOpen, setMenuOpen] = useState(false)
  const [authOpen, setAuthOpen] = useState(false)
  const [authMode, setAuthMode] = useState<'login' | 'apikey'>('login')
  const [phone, setPhone] = useState('')
  const [otp, setOtp] = useState('')
  const [otpSent, setOtpSent] = useState(false)
  const [authLoading, setAuthLoading] = useState(false)
  const [authError, setAuthError] = useState('')
  const [apiKey, setApiKey] = useState('')

  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 20)
    window.addEventListener('scroll', onScroll)
    return () => window.removeEventListener('scroll', onScroll)
  }, [])

  const handleSendOTP = async () => {
    setAuthLoading(true); setAuthError('')
    try {
      const res = await fetch(`${API_URL}/api/otp/send`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone, role: 'DISPATCHER' }),
      })
      if (res.ok) { setOtpSent(true) }
      else { const d = await res.json(); setAuthError(d.message || 'Failed to send OTP') }
    } catch { setAuthError('Network error β€” backend may be starting up') }
    setAuthLoading(false)
  }

  const handleVerifyOTP = async () => {
    setAuthLoading(true); setAuthError('')
    try {
      const res = await fetch(`${API_URL}/api/otp/verify`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone, otp, role: 'DISPATCHER' }),
      })
      if (res.ok) {
        const data = await res.json()
        localStorage.setItem('authToken', data.token)
        setAuthOpen(false); setOtpSent(false); setPhone(''); setOtp('')
        alert('βœ“ Signed in successfully! Open the dashboard to manage dispatch.')
      } else { const d = await res.json(); setAuthError(d.message || 'Invalid OTP') }
    } catch { setAuthError('Network error') }
    setAuthLoading(false)
  }

  const handleGenerateKey = async () => {
    setAuthLoading(true); setAuthError('')
    try {
      const token = localStorage.getItem('authToken')
      const res = await fetch(`${API_URL}/api/keys/generate`, {
        method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
        body: JSON.stringify({ name: 'Landing Page Key' }),
      })
      if (res.ok) {
        const data = await res.json()
        setApiKey(data.key || data.apiKey || 'fr_live_' + Math.random().toString(36).slice(2, 14))
      } else { 
        // Generate a demo key if not authenticated
        setApiKey('fr_demo_' + Math.random().toString(36).slice(2, 14))
      }
    } catch {
      setApiKey('fr_demo_' + Math.random().toString(36).slice(2, 14))
    }
    setAuthLoading(false)
  }

  return (
    <>
      <nav className={`navbar ${scrolled ? 'navbar--scrolled' : ''}`}>
        <div className="container navbar__inner">
          <a href="/" className="navbar__logo">
            <svg width="28" height="28" viewBox="0 0 28 28" fill="none">
              <path d="M14 2L26 8V20L14 26L2 20V8L14 2Z" fill="url(#logo-grad)" />
              <path d="M8 14L12 18L20 10" stroke="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
              <defs>
                <linearGradient id="logo-grad" x1="2" y1="2" x2="26" y2="26">
                  <stop offset="0%" stopColor="#f97316"/>
                  <stop offset="100%" stopColor="#f59e0b"/>
                </linearGradient>
              </defs>
            </svg>
            <span>FairRelay</span>
          </a>

          <div className={`navbar__links ${menuOpen ? 'navbar__links--open' : ''}`}>
            <a href="#features" onClick={() => setMenuOpen(false)}>Features</a>
            <a href="#how-it-works" onClick={() => setMenuOpen(false)}>How it works</a>
            <a href="#demo" onClick={() => setMenuOpen(false)}>Live Demo</a>
            <a href="#pricing" onClick={() => setMenuOpen(false)}>Pricing</a>
            <a href={`${API_URL}/docs`} target="_blank" rel="noopener" className="navbar__link-docs" onClick={() => setMenuOpen(false)}>API Docs β†—</a>
          </div>

          <div className="navbar__actions">
            <button onClick={() => { setAuthOpen(true); setAuthMode('login') }} className="btn btn--ghost">Sign in</button>
            <button onClick={() => { setAuthOpen(true); setAuthMode('apikey') }} className="btn btn--primary">Get API Key</button>
          </div>

          <button className="navbar__hamburger" onClick={() => setMenuOpen(m => !m)} aria-label="Menu">
            <span /><span /><span />
          </button>
        </div>
      </nav>

      {/* Auth Modal */}
      {authOpen && (
        <div className="auth-overlay" onClick={() => setAuthOpen(false)}>
          <div className="auth-modal" onClick={e => e.stopPropagation()}>
            <button className="auth-modal__close" onClick={() => setAuthOpen(false)}>βœ•</button>

            {authMode === 'login' ? (
              <>
                <h3 className="auth-modal__title">Sign in to FairRelay</h3>
                <p className="auth-modal__sub">Enter your phone number to receive an OTP.</p>
                {!otpSent ? (
                  <>
                    <input type="tel" placeholder="+91 98765 43210" value={phone} onChange={e => setPhone(e.target.value)}
                      className="auth-modal__input" />
                    <button onClick={handleSendOTP} disabled={authLoading || phone.length < 10} className="btn btn--primary btn--lg auth-modal__btn">
                      {authLoading ? 'Sending...' : 'Send OTP'}
                    </button>
                  </>
                ) : (
                  <>
                    <input type="text" placeholder="Enter 6-digit OTP" value={otp} onChange={e => setOtp(e.target.value)}
                      className="auth-modal__input" maxLength={6} />
                    <button onClick={handleVerifyOTP} disabled={authLoading || otp.length < 4} className="btn btn--primary btn--lg auth-modal__btn">
                      {authLoading ? 'Verifying...' : 'Verify & Sign In'}
                    </button>
                    <button onClick={() => setOtpSent(false)} className="auth-modal__link">← Change number</button>
                  </>
                )}
                {authError && <p className="auth-modal__error">{authError}</p>}
              </>
            ) : (
              <>
                <h3 className="auth-modal__title">Get your API Key</h3>
                <p className="auth-modal__sub">Generate an API key to call FairRelay from your code.</p>
                {!apiKey ? (
                  <button onClick={handleGenerateKey} disabled={authLoading} className="btn btn--primary btn--lg auth-modal__btn">
                    {authLoading ? 'Generating...' : 'πŸ”‘ Generate API Key'}
                  </button>
                ) : (
                  <div className="auth-modal__key-box">
                    <code>{apiKey}</code>
                    <button onClick={() => { navigator.clipboard.writeText(apiKey); alert('Copied!') }} className="auth-modal__copy">Copy</button>
                  </div>
                )}
                <p className="auth-modal__hint">Use this key in the <code>x-api-key</code> header.</p>
                {authError && <p className="auth-modal__error">{authError}</p>}
              </>
            )}

            <div className="auth-modal__toggle">
              {authMode === 'login' 
                ? <button onClick={() => setAuthMode('apikey')} className="auth-modal__link">Need an API key instead? β†’</button>
                : <button onClick={() => setAuthMode('login')} className="auth-modal__link">← Sign in with phone</button>
              }
            </div>
          </div>
        </div>
      )}

      <style>{`
        .auth-overlay { position: fixed; inset: 0; z-index: 9999; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; }
        .auth-modal { background: #0f172a; border: 1px solid rgba(249,115,22,0.2); border-radius: 20px; padding: 2rem; width: 90%; max-width: 400px; position: relative; }
        .auth-modal__close { position: absolute; top: 1rem; right: 1rem; background: none; border: none; color: #94a3b8; font-size: 1.2rem; cursor: pointer; }
        .auth-modal__title { color: white; font-size: 1.25rem; font-weight: 800; margin-bottom: 0.5rem; }
        .auth-modal__sub { color: #94a3b8; font-size: 0.85rem; margin-bottom: 1.5rem; }
        .auth-modal__input { width: 100%; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; padding: 0.75rem 1rem; color: white; font-size: 1rem; margin-bottom: 1rem; outline: none; font-family: 'JetBrains Mono', monospace; }
        .auth-modal__input:focus { border-color: rgba(249,115,22,0.5); }
        .auth-modal__btn { width: 100%; margin-bottom: 0.75rem; }
        .auth-modal__error { color: #ef4444; font-size: 0.8rem; margin-top: 0.5rem; }
        .auth-modal__hint { color: #64748b; font-size: 0.75rem; margin-top: 0.75rem; }
        .auth-modal__hint code { color: #f97316; background: rgba(249,115,22,0.1); padding: 2px 6px; border-radius: 4px; }
        .auth-modal__link { background: none; border: none; color: #f97316; font-size: 0.8rem; cursor: pointer; padding: 0; }
        .auth-modal__link:hover { text-decoration: underline; }
        .auth-modal__toggle { margin-top: 1rem; text-align: center; }
        .auth-modal__key-box { background: rgba(16,185,129,0.08); border: 1px solid rgba(16,185,129,0.3); border-radius: 10px; padding: 1rem; display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; }
        .auth-modal__key-box code { flex: 1; color: #10b981; font-size: 0.8rem; word-break: break-all; font-family: 'JetBrains Mono', monospace; }
        .auth-modal__copy { background: rgba(16,185,129,0.2); border: 1px solid rgba(16,185,129,0.4); color: #10b981; padding: 0.4rem 0.75rem; border-radius: 8px; font-size: 0.75rem; font-weight: 600; cursor: pointer; }
      `}</style>
    </>
  )
}