A web app for writing and sharing 301+ character Bluesky posts.
1import { useState } from 'react'
2import './App.css'
3import Login from './components/Login'
4import { UnifiedAuthProvider } from './providers/UnifiedAuthProvider'
5import { AtpAgent } from '@atproto/api'
6// import { TID } from '@atproto/common'
7
8function App() {
9 const agent = new AtpAgent({ service: 'https://skeetlonger.app' })
10
11 const [postText, setPostText] = useState('')
12 const charCount = postText.length
13
14 const handleSubmit = async () => {
15 // Generate a time-based key for our record
16 const rkey = "self"
17
18 await agent.com.atproto.repo.putRecord({
19 repo: agent.assertDid, // The user
20 collection: 'app.skeetlonger.post', // The collection
21 rkey, // The record key
22 record: { // The record value
23 post: postText,
24 createdAt: new Date().toISOString()
25 }
26 })
27
28 // setError(null);
29 // try {
30 // localStorage.setItem("lastHandle", user);
31 // await loginWithPassword(user, password, `https://${serviceURL}`);
32 // } catch (err) {
33 // setError("Login failed. Check your handle and App Password.");
34 // }
35 };
36
37 return (
38 <UnifiedAuthProvider>
39 <div style={{ position: 'fixed', top: '1rem', right: '1rem', zIndex: 9999 }}>
40 <Login compact={true} />
41 </div>
42
43 <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '5rem 1rem', backgroundColor: '#242424' }}>
44 <div style={{ width: '100%', maxWidth: '72rem' }}>
45 {/* Title */}
46 <h1 style={{ fontSize: '2.25rem', fontWeight: 'bold', textAlign: 'center', marginBottom: '0.5rem', color: '#f3f4f6' }}>
47 SkeetLonger
48 </h1>
49 <p style={{ textAlign: 'center', marginBottom: '2rem', color: '#9ca3af' }}>
50 Post longer content to Bluesky
51 </p>
52
53 {/* Text Editor Card */}
54 <div style={{ padding: '1.5rem', borderRadius: '0.75rem', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)', border: '1px solid #e5e7eb', backgroundColor: '#1a1a1a', borderColor: '#374151' }}>
55 <textarea
56 value={postText}
57 onChange={(e) => setPostText(e.target.value)}
58 placeholder="What's on your mind? Write as much as you'd like..."
59 style={{ width: '32rem', height: '32rem', padding: '0.75rem 1rem', borderRadius: '0.5rem', border: '1px solid #4b5563', fontSize: '1rem', lineHeight: '1.75', resize: 'none', backgroundColor: '#111827', color: '#f3f4f6', boxSizing: 'border-box' }}
60 className="placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
61 />
62
63 {/* Footer with character count and post button */}
64 <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: '1rem' }}>
65 <div style={{ fontSize: '0.875rem' }}>
66 <span style={{ fontWeight: '500', color: charCount > 300 ? '#3b82f6' : '#9ca3af' }}>
67 {charCount} characters
68 </span>
69 </div>
70
71 <button
72 onClick={() => handleSubmit()}
73 type='submit'
74 disabled={charCount < 300}
75 style={{ padding: '0.625rem 1.5rem', borderRadius: '0.5rem', fontSize: '0.875rem', fontWeight: '600', border: 'none', cursor: charCount === 0 ? 'not-allowed' : 'pointer', transition: 'background-color 0.2s', backgroundColor: charCount === 0 ? '#6b7280' : '#2563eb', color: 'white' }}
76 onMouseEnter={(e) => { if (charCount !== 0) e.currentTarget.style.backgroundColor = '#1d4ed8'; }}
77 onMouseLeave={(e) => { if (charCount !== 0) e.currentTarget.style.backgroundColor = '#2563eb'; }}
78 >
79 Post
80 </button>
81 </div>
82 </div>
83 </div>
84 </div>
85 </UnifiedAuthProvider>
86 )
87}
88
89export default App