mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-08 07:43:08 +00:00
wip
This commit is contained in:
parent
f8aed8e199
commit
27a5e5c500
@ -1,2 +1,3 @@
|
|||||||
VITE_REOWN_SECRET=
|
VITE_REOWN_SECRET=
|
||||||
VITE_OPCHAN_MOCK_ORDINAL_CHECK=
|
VITE_OPCHAN_MOCK_ORDINAL_CHECK=
|
||||||
|
VITE_ORDISCAN_API_KEY=
|
||||||
19
package-lock.json
generated
19
package-lock.json
generated
@ -53,6 +53,7 @@
|
|||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
|
"ordiscan": "^1.3.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@ -12582,6 +12583,18 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ordiscan": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ordiscan/-/ordiscan-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-IV8yayKGIRtfkI3rQ1gu+aKQ6UmNHXB910qUuWrraCcuk6U/YkE+6X8Bh+jW2P8L8/lOfYA6DLVeKCVPkj8c6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"zod": "^3.24.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ox": {
|
"node_modules/ox": {
|
||||||
"version": "0.6.9",
|
"version": "0.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz",
|
||||||
@ -15870,9 +15883,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.23.8",
|
"version": "3.25.76",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
|||||||
@ -60,6 +60,7 @@
|
|||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
|
"ordiscan": "^1.3.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import Dashboard from './pages/Dashboard';
|
|||||||
import Index from './pages/Index';
|
import Index from './pages/Index';
|
||||||
import ProfilePage from './pages/ProfilePage';
|
import ProfilePage from './pages/ProfilePage';
|
||||||
import BookmarksPage from './pages/BookmarksPage';
|
import BookmarksPage from './pages/BookmarksPage';
|
||||||
|
import OrdiscanPage from './pages/OrdiscanPage';
|
||||||
import { appkitConfig } from './lib/wallet/config';
|
import { appkitConfig } from './lib/wallet/config';
|
||||||
import { WagmiProvider } from 'wagmi';
|
import { WagmiProvider } from 'wagmi';
|
||||||
import { config } from './lib/wallet/config';
|
import { config } from './lib/wallet/config';
|
||||||
@ -52,6 +53,7 @@ const App = () => (
|
|||||||
<Route path="/post/:postId" element={<PostPage />} />
|
<Route path="/post/:postId" element={<PostPage />} />
|
||||||
<Route path="/profile" element={<ProfilePage />} />
|
<Route path="/profile" element={<ProfilePage />} />
|
||||||
<Route path="/bookmarks" element={<BookmarksPage />} />
|
<Route path="/bookmarks" element={<BookmarksPage />} />
|
||||||
|
<Route path="/ordiscan" element={<OrdiscanPage />} />
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
import { OrdinalApiResponse } from './types';
|
|
||||||
|
|
||||||
const BASE_URL = 'https://dashboard.logos.co/api/operators/wallet';
|
|
||||||
|
|
||||||
export class OrdinalAPI {
|
|
||||||
/**
|
|
||||||
* Fetches Ordinal operator details for a given Bitcoin address.
|
|
||||||
* @param address - The Bitcoin address to query.
|
|
||||||
* @returns A promise that resolves with the API response.
|
|
||||||
*/
|
|
||||||
async getOperatorDetails(address: string): Promise<OrdinalApiResponse> {
|
|
||||||
if (import.meta.env.VITE_OPCHAN_MOCK_ORDINAL_CHECK === 'true') {
|
|
||||||
console.log(
|
|
||||||
`[DEV] Bypassing ordinal verification for address: ${address}`
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
has_operators: true,
|
|
||||||
error_message: '',
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `${BASE_URL}/${address}/detail/`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { Accept: 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorBody = await response.text().catch(() => '');
|
|
||||||
throw new Error(
|
|
||||||
`HTTP error! status: ${response.status}, message: ${errorBody || response.statusText}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: OrdinalApiResponse = await response.json();
|
|
||||||
|
|
||||||
if (data.error_message) {
|
|
||||||
console.warn(
|
|
||||||
`API returned an error message for address ${address}: ${data.error_message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`Failed to fetch ordinal details for address ${address}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
export interface OrdinalDetail {
|
|
||||||
name: string;
|
|
||||||
archetype_name: string;
|
|
||||||
comp: string;
|
|
||||||
background: string;
|
|
||||||
skin: string;
|
|
||||||
helmet: string;
|
|
||||||
jacket: string;
|
|
||||||
image_200_url: string;
|
|
||||||
image_200_jpeg_url: string;
|
|
||||||
image_400_url: string;
|
|
||||||
image_400_jpeg_url: string;
|
|
||||||
image_1024_url: string;
|
|
||||||
image_1024_jpeg_url: string;
|
|
||||||
image_2048_url: string;
|
|
||||||
image_2048_jpeg_url: string;
|
|
||||||
image_pixalated_url: string;
|
|
||||||
mp4_url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OrdinalApiResponse {
|
|
||||||
has_operators: boolean;
|
|
||||||
error_message: string;
|
|
||||||
data: OrdinalDetail[];
|
|
||||||
}
|
|
||||||
17
src/lib/services/OrdinalService.ts
Normal file
17
src/lib/services/OrdinalService.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Ordiscan } from 'ordiscan';
|
||||||
|
|
||||||
|
console.log(import.meta.env.VITE_ORDISCAN_API_KEY);
|
||||||
|
const ordiscan = new Ordiscan(import.meta.env.VITE_ORDISCAN_API_KEY);
|
||||||
|
|
||||||
|
export class OrdinalAPI {
|
||||||
|
/**
|
||||||
|
* Fetches Ordinal operator details for a given Bitcoin address.
|
||||||
|
* @param address - The Bitcoin address to query.
|
||||||
|
* @returns A promise that resolves with the API response.
|
||||||
|
*/
|
||||||
|
async getOperatorDetails(address: string) {
|
||||||
|
const response = await ordiscan.address.getInscriptions({address: address})
|
||||||
|
console.log(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/pages/OrdiscanPage.tsx
Normal file
110
src/pages/OrdiscanPage.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
import { OrdinalAPI } from '@/lib/services/OrdinalService';
|
||||||
|
|
||||||
|
const ordinalAPI = new OrdinalAPI();
|
||||||
|
|
||||||
|
export default function OrdiscanPage() {
|
||||||
|
const [address, setAddress] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState<unknown[] | Record<string, unknown> | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
if (!address.trim()) {
|
||||||
|
setError('Please enter a Bitcoin address');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setData(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await ordinalAPI.getOperatorDetails(address.trim());
|
||||||
|
setData(result);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Failed to fetch data');
|
||||||
|
console.error('Ordiscan API error:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-6 max-w-4xl">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-2xl font-bold">
|
||||||
|
Ordiscan Explorer
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Search for Bitcoin inscriptions and ordinal operator details
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
placeholder="Enter Bitcoin address (e.g., bc1p...)"
|
||||||
|
value={address}
|
||||||
|
onChange={e => setAddress(e.target.value)}
|
||||||
|
onKeyDown={e => e.key === 'Enter' && handleSearch()}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={handleSearch}
|
||||||
|
disabled={loading}
|
||||||
|
className="min-w-[100px]"
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Loading
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Search'
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">Inscriptions Data</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<pre className="bg-muted p-4 rounded-lg overflow-auto text-sm">
|
||||||
|
{JSON.stringify(data, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!data && !loading && !error && (
|
||||||
|
<div className="text-center text-muted-foreground py-8">
|
||||||
|
Enter a Bitcoin address to search for inscriptions
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -59,7 +59,9 @@ export default function ProfilePage() {
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [callSign, setCallSign] = useState('');
|
const [callSign, setCallSign] = useState('');
|
||||||
const [displayPreference, setDisplayPreference] = useState(EDisplayPreference.WALLET_ADDRESS);
|
const [displayPreference, setDisplayPreference] = useState(
|
||||||
|
EDisplayPreference.WALLET_ADDRESS
|
||||||
|
);
|
||||||
const [walletWizardOpen, setWalletWizardOpen] = useState(false);
|
const [walletWizardOpen, setWalletWizardOpen] = useState(false);
|
||||||
|
|
||||||
// Initialize and update local state when user data changes
|
// Initialize and update local state when user data changes
|
||||||
@ -67,7 +69,10 @@ export default function ProfilePage() {
|
|||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
// Use the same data source as the display (userInfo) for consistency
|
// Use the same data source as the display (userInfo) for consistency
|
||||||
const currentCallSign = userInfo.callSign || currentUser.callSign || '';
|
const currentCallSign = userInfo.callSign || currentUser.callSign || '';
|
||||||
const currentDisplayPreference = userInfo.displayPreference || currentUser.displayPreference || EDisplayPreference.WALLET_ADDRESS;
|
const currentDisplayPreference =
|
||||||
|
userInfo.displayPreference ||
|
||||||
|
currentUser.displayPreference ||
|
||||||
|
EDisplayPreference.WALLET_ADDRESS;
|
||||||
|
|
||||||
setCallSign(currentCallSign);
|
setCallSign(currentCallSign);
|
||||||
setDisplayPreference(currentDisplayPreference);
|
setDisplayPreference(currentDisplayPreference);
|
||||||
@ -174,7 +179,10 @@ export default function ProfilePage() {
|
|||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
// Reset to the same data source as display for consistency
|
// Reset to the same data source as display for consistency
|
||||||
const currentCallSign = userInfo.callSign || currentUser.callSign || '';
|
const currentCallSign = userInfo.callSign || currentUser.callSign || '';
|
||||||
const currentDisplayPreference = userInfo.displayPreference || currentUser.displayPreference || EDisplayPreference.WALLET_ADDRESS;
|
const currentDisplayPreference =
|
||||||
|
userInfo.displayPreference ||
|
||||||
|
currentUser.displayPreference ||
|
||||||
|
EDisplayPreference.WALLET_ADDRESS;
|
||||||
|
|
||||||
setCallSign(currentCallSign);
|
setCallSign(currentCallSign);
|
||||||
setDisplayPreference(currentDisplayPreference);
|
setDisplayPreference(currentDisplayPreference);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user