nice
This commit is contained in:
parent
2ecadcf19f
commit
43e4b38e96
@ -14,6 +14,9 @@ const nextConfig: NextConfig = {
|
|||||||
// your project has ESLint errors.
|
// your project has ESLint errors.
|
||||||
ignoreDuringBuilds: true,
|
ignoreDuringBuilds: true,
|
||||||
},
|
},
|
||||||
|
// Enable React's production mode in both development and production
|
||||||
|
// This helps eliminate some development-only errors in the UI
|
||||||
|
reactStrictMode: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
@ -1,10 +1,97 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { Account, Registry, parseGasAndFees } from '@cerc-io/registry-sdk';
|
import { Account, Registry, parseGasAndFees } from '@cerc-io/registry-sdk';
|
||||||
import { GasPrice } from '@cosmjs/stargate';
|
import { GasPrice } from '@cosmjs/stargate';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
// Sleep helper function
|
// Sleep helper function
|
||||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
// Extract repo name from URL
|
||||||
|
const extractRepoInfo = (url: string): { repoName: string, repoUrl: string, provider: string } => {
|
||||||
|
try {
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
const pathParts = parsedUrl.pathname.split('/').filter(part => part);
|
||||||
|
|
||||||
|
// GitHub repository URL pattern
|
||||||
|
if (parsedUrl.hostname === 'github.com' && pathParts.length >= 2) {
|
||||||
|
return {
|
||||||
|
repoName: pathParts[1],
|
||||||
|
repoUrl: `https://github.com/${pathParts[0]}/${pathParts[1]}`,
|
||||||
|
provider: 'github'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitLab repository URL pattern
|
||||||
|
if ((parsedUrl.hostname === 'gitlab.com' || parsedUrl.hostname.includes('gitlab')) && pathParts.length >= 2) {
|
||||||
|
return {
|
||||||
|
repoName: pathParts[pathParts.length - 1],
|
||||||
|
repoUrl: url,
|
||||||
|
provider: 'gitlab'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitbucket repository URL pattern
|
||||||
|
if (parsedUrl.hostname === 'bitbucket.org' && pathParts.length >= 2) {
|
||||||
|
return {
|
||||||
|
repoName: pathParts[1],
|
||||||
|
repoUrl: `https://bitbucket.org/${pathParts[0]}/${pathParts[1]}`,
|
||||||
|
provider: 'bitbucket'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other URLs, try to extract a meaningful name from the hostname
|
||||||
|
const hostnameWithoutTLD = parsedUrl.hostname.split('.')[0];
|
||||||
|
return {
|
||||||
|
repoName: hostnameWithoutTLD,
|
||||||
|
repoUrl: url,
|
||||||
|
provider: 'other'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse URL, using fallback name', error);
|
||||||
|
return {
|
||||||
|
repoName: 'webapp',
|
||||||
|
repoUrl: url,
|
||||||
|
provider: 'other'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch latest commit hash from GitHub repository
|
||||||
|
const fetchLatestCommitHash = async (repoUrl: string, provider: string): Promise<{ fullHash: string, shortHash: string }> => {
|
||||||
|
try {
|
||||||
|
// Handle GitHub repositories
|
||||||
|
if (provider === 'github') {
|
||||||
|
// Extract owner and repo from GitHub URL
|
||||||
|
const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/);
|
||||||
|
if (match) {
|
||||||
|
const [, owner, repo] = match;
|
||||||
|
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/main`;
|
||||||
|
|
||||||
|
const response = await axios.get(apiUrl);
|
||||||
|
if (response.data && response.data.sha) {
|
||||||
|
// Return both full hash and short hash (7 characters)
|
||||||
|
return {
|
||||||
|
fullHash: response.data.sha,
|
||||||
|
shortHash: response.data.sha.substring(0, 7)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-GitHub repositories or if fetching fails, return a default value
|
||||||
|
return {
|
||||||
|
fullHash: 'main',
|
||||||
|
shortHash: 'main'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to fetch latest commit hash:', error);
|
||||||
|
return {
|
||||||
|
fullHash: 'main',
|
||||||
|
shortHash: 'main'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Registry transaction retry helper
|
// Registry transaction retry helper
|
||||||
const registryTransactionWithRetry = async (
|
const registryTransactionWithRetry = async (
|
||||||
txFn: () => Promise<unknown>,
|
txFn: () => Promise<unknown>,
|
||||||
@ -41,7 +128,6 @@ export async function POST(request: NextRequest) {
|
|||||||
'REGISTRY_BOND_ID',
|
'REGISTRY_BOND_ID',
|
||||||
'REGISTRY_AUTHORITY',
|
'REGISTRY_AUTHORITY',
|
||||||
'REGISTRY_USER_KEY',
|
'REGISTRY_USER_KEY',
|
||||||
'APP_NAME',
|
|
||||||
'DEPLOYER_LRN'
|
'DEPLOYER_LRN'
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -54,6 +140,46 @@ export async function POST(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract repository information from URL
|
||||||
|
const { repoName, repoUrl, provider } = extractRepoInfo(url);
|
||||||
|
console.log(`Extracted repo info - Name: ${repoName}, URL: ${repoUrl}, Provider: ${provider}`);
|
||||||
|
|
||||||
|
// Fetch latest commit hash (or default to 'main' if unable to fetch)
|
||||||
|
const { fullHash, shortHash } = await fetchLatestCommitHash(repoUrl, provider);
|
||||||
|
console.log(`Using commit hash - Full: ${fullHash}, Short: ${shortHash}`);
|
||||||
|
|
||||||
|
// Use the repository name as the app name
|
||||||
|
const appName = repoName;
|
||||||
|
console.log(`Using app name: ${appName}`);
|
||||||
|
|
||||||
|
// Sanitize the app name to ensure it's DNS-compatible (only alphanumeric and dashes)
|
||||||
|
const sanitizedAppName = appName.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
||||||
|
|
||||||
|
// Create DNS name in format: app_name-shortcommithash
|
||||||
|
const dnsName = `${sanitizedAppName}-${shortHash}`;
|
||||||
|
console.log(`DNS name: ${dnsName} (sanitized from: ${appName})`);
|
||||||
|
|
||||||
|
// Ensure the DNS name doesn't have consecutive dashes or start/end with a dash
|
||||||
|
let cleanDnsName = dnsName
|
||||||
|
.replace(/--+/g, '-') // Replace consecutive dashes with a single dash
|
||||||
|
.replace(/^-+|-+$/g, ''); // Remove leading and trailing dashes
|
||||||
|
|
||||||
|
// Ensure DNS name is valid (63 chars max per label, all lowercase, starts with a letter)
|
||||||
|
if (cleanDnsName.length > 63) {
|
||||||
|
// If too long, truncate but keep the commit hash part
|
||||||
|
const hashPart = `-${shortHash}`;
|
||||||
|
const maxAppNameLength = 63 - hashPart.length;
|
||||||
|
cleanDnsName = sanitizedAppName.substring(0, maxAppNameLength) + hashPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the DNS name ended up empty (unlikely) or doesn't start with a letter (possible),
|
||||||
|
// add a prefix to make it valid
|
||||||
|
if (!cleanDnsName || !/^[a-z]/.test(cleanDnsName)) {
|
||||||
|
cleanDnsName = `app-${cleanDnsName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Final DNS name: ${cleanDnsName}`);
|
||||||
|
|
||||||
// Set up Registry config
|
// Set up Registry config
|
||||||
const config = {
|
const config = {
|
||||||
chainId: process.env.REGISTRY_CHAIN_ID!,
|
chainId: process.env.REGISTRY_CHAIN_ID!,
|
||||||
@ -74,13 +200,10 @@ export async function POST(request: NextRequest) {
|
|||||||
privateKey: '[REDACTED]', // Don't log the private key
|
privateKey: '[REDACTED]', // Don't log the private key
|
||||||
});
|
});
|
||||||
|
|
||||||
const appName = process.env.APP_NAME || 'atom-deploy';
|
|
||||||
const deployerLrn = process.env.DEPLOYER_LRN!;
|
const deployerLrn = process.env.DEPLOYER_LRN!;
|
||||||
|
|
||||||
// Create Registry client instance
|
// Create Registry client instance
|
||||||
// Use the GasPrice from @cosmjs/stargate
|
|
||||||
const gasPrice = GasPrice.fromString('0.001alnt');
|
const gasPrice = GasPrice.fromString('0.001alnt');
|
||||||
|
|
||||||
console.log('Using manual gas price:', gasPrice);
|
console.log('Using manual gas price:', gasPrice);
|
||||||
|
|
||||||
const registry = new Registry(
|
const registry = new Registry(
|
||||||
@ -100,10 +223,10 @@ export async function POST(request: NextRequest) {
|
|||||||
const applicationRecord = {
|
const applicationRecord = {
|
||||||
type: 'ApplicationRecord',
|
type: 'ApplicationRecord',
|
||||||
name: appName,
|
name: appName,
|
||||||
version: '0.0.1',
|
version: '1.0.0',
|
||||||
app_type: 'webapp',
|
app_type: 'webapp',
|
||||||
repository: [url],
|
repository: [repoUrl],
|
||||||
repository_ref: 'main', // Default reference or commit hash
|
repository_ref: fullHash,
|
||||||
app_version: '0.0.1'
|
app_version: '0.0.1'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,13 +291,13 @@ export async function POST(request: NextRequest) {
|
|||||||
registry.setName(
|
registry.setName(
|
||||||
{
|
{
|
||||||
cid: applicationRecordId,
|
cid: applicationRecordId,
|
||||||
lrn: `${lrn}@main` // Using 'main' as default ref
|
lrn: `${lrn}@${fullHash}`
|
||||||
},
|
},
|
||||||
config.privateKey,
|
config.privateKey,
|
||||||
fee
|
fee
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
console.log(`Set name mapping: ${lrn}@main -> ${applicationRecordId}`);
|
console.log(`Set name mapping: ${lrn}@${fullHash} -> ${applicationRecordId}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error setting name mappings:', err);
|
console.error('Error setting name mappings:', err);
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
@ -192,11 +315,16 @@ export async function POST(request: NextRequest) {
|
|||||||
name: appName,
|
name: appName,
|
||||||
application: lrn,
|
application: lrn,
|
||||||
deployer: deployerLrn,
|
deployer: deployerLrn,
|
||||||
dns: new URL(url).hostname,
|
dns: cleanDnsName,
|
||||||
|
config: {
|
||||||
|
env: {
|
||||||
|
LACONIC_HOSTED_CONFIG_laconicd_chain_id: process.env.REGISTRY_CHAIN_ID || 'laconic-testnet-2'
|
||||||
|
}
|
||||||
|
},
|
||||||
meta: {
|
meta: {
|
||||||
note: `Added via ATOM-Deploy @ ${timestamp}`,
|
note: `Added via ATOM-Deploy @ ${timestamp}`,
|
||||||
repository: url,
|
repository: repoUrl,
|
||||||
repository_ref: 'main', // Default reference
|
repository_ref: fullHash,
|
||||||
},
|
},
|
||||||
payment: txHash,
|
payment: txHash,
|
||||||
};
|
};
|
||||||
@ -240,7 +368,12 @@ export async function POST(request: NextRequest) {
|
|||||||
id: deploymentRequestId,
|
id: deploymentRequestId,
|
||||||
applicationRecordId: applicationRecordId,
|
applicationRecordId: applicationRecordId,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
lrn: lrn
|
lrn: lrn,
|
||||||
|
dns: cleanDnsName,
|
||||||
|
appName: appName,
|
||||||
|
repoUrl: repoUrl,
|
||||||
|
commitHash: fullHash,
|
||||||
|
shortCommitHash: shortHash
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create application deployment request:', error);
|
console.error('Failed to create application deployment request:', error);
|
||||||
|
@ -1,8 +1,35 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
/* Base colors */
|
||||||
--foreground: #171717;
|
--background: #f8fafc;
|
||||||
|
--foreground: #0f172a;
|
||||||
|
|
||||||
|
/* UI elements */
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--card-border: #e2e8f0;
|
||||||
|
|
||||||
|
/* Primary colors with high contrast */
|
||||||
|
--primary: #2563eb;
|
||||||
|
--primary-hover: #1d4ed8;
|
||||||
|
--primary-foreground: #ffffff;
|
||||||
|
|
||||||
|
/* Status colors */
|
||||||
|
--success: #10b981;
|
||||||
|
--success-light: #d1fae5;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--warning-light: #fef3c7;
|
||||||
|
--error: #ef4444;
|
||||||
|
--error-light: #fee2e2;
|
||||||
|
|
||||||
|
/* Neutral shades */
|
||||||
|
--muted: #64748b;
|
||||||
|
--muted-foreground: #94a3b8;
|
||||||
|
--muted-light: #f1f5f9;
|
||||||
|
|
||||||
|
/* Inputs */
|
||||||
|
--input-border: #cbd5e1;
|
||||||
|
--input-focus: #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@ -14,8 +41,29 @@
|
|||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
--background: #0a0a0a;
|
--background: #0f172a;
|
||||||
--foreground: #ededed;
|
--foreground: #f8fafc;
|
||||||
|
|
||||||
|
--card-bg: #1e293b;
|
||||||
|
--card-border: #334155;
|
||||||
|
|
||||||
|
--primary: #3b82f6;
|
||||||
|
--primary-hover: #2563eb;
|
||||||
|
--primary-foreground: #ffffff;
|
||||||
|
|
||||||
|
--success: #10b981;
|
||||||
|
--success-light: #065f46;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--warning-light: #92400e;
|
||||||
|
--error: #ef4444;
|
||||||
|
--error-light: #991b1b;
|
||||||
|
|
||||||
|
--muted: #94a3b8;
|
||||||
|
--muted-foreground: #cbd5e1;
|
||||||
|
--muted-light: #1e293b;
|
||||||
|
|
||||||
|
--input-border: #475569;
|
||||||
|
--input-focus: #60a5fa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,3 +72,19 @@ body {
|
|||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes appear {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-appear {
|
||||||
|
animation: appear 0.2s ease-out forwards;
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import ErrorBoundaryWrapper from "../components/ErrorBoundaryWrapper";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@ -27,6 +28,7 @@ export default function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
|
<ErrorBoundaryWrapper />
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -19,6 +19,11 @@ export default function Home() {
|
|||||||
const [recordId, setRecordId] = useState<string | null>(null);
|
const [recordId, setRecordId] = useState<string | null>(null);
|
||||||
const [appRecordId, setAppRecordId] = useState<string | null>(null);
|
const [appRecordId, setAppRecordId] = useState<string | null>(null);
|
||||||
const [lrn, setLrn] = useState<string | null>(null);
|
const [lrn, setLrn] = useState<string | null>(null);
|
||||||
|
const [dns, setDns] = useState<string | null>(null);
|
||||||
|
const [appName, setAppName] = useState<string | null>(null);
|
||||||
|
const [repoUrl, setRepoUrl] = useState<string | null>(null);
|
||||||
|
const [commitHash, setCommitHash] = useState<string | null>(null);
|
||||||
|
const [shortCommitHash, setShortCommitHash] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleConnect = (address: string) => {
|
const handleConnect = (address: string) => {
|
||||||
@ -58,6 +63,21 @@ export default function Home() {
|
|||||||
if (result.lrn) {
|
if (result.lrn) {
|
||||||
setLrn(result.lrn);
|
setLrn(result.lrn);
|
||||||
}
|
}
|
||||||
|
if (result.dns) {
|
||||||
|
setDns(result.dns);
|
||||||
|
}
|
||||||
|
if (result.appName) {
|
||||||
|
setAppName(result.appName);
|
||||||
|
}
|
||||||
|
if (result.repoUrl) {
|
||||||
|
setRepoUrl(result.repoUrl);
|
||||||
|
}
|
||||||
|
if (result.commitHash) {
|
||||||
|
setCommitHash(result.commitHash);
|
||||||
|
}
|
||||||
|
if (result.shortCommitHash) {
|
||||||
|
setShortCommitHash(result.shortCommitHash);
|
||||||
|
}
|
||||||
setStatus('success');
|
setStatus('success');
|
||||||
} else {
|
} else {
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
@ -76,31 +96,56 @@ export default function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen flex flex-col items-center justify-center p-6">
|
<main className="min-h-screen flex flex-col items-center justify-center p-6">
|
||||||
<div className="max-w-lg w-full bg-white p-8 rounded-lg shadow-lg">
|
<div style={{ background: 'var(--card-bg)', borderColor: 'var(--card-border)' }}
|
||||||
<h1 className="text-2xl font-bold mb-8 text-center">ATOM Deploy - Laconic Registry</h1>
|
className="max-w-xl w-full p-8 rounded-xl shadow-lg border">
|
||||||
|
<h1 className="text-2xl font-bold mb-8 text-center" style={{ color: 'var(--foreground)' }}>
|
||||||
|
ATOM Deploy - Laconic Registry
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="mb-10 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
|
||||||
<h2 className="text-lg font-semibold mb-4">1. Connect Your Wallet</h2>
|
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||||
|
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||||
|
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>1</span>
|
||||||
|
Connect Your Wallet
|
||||||
|
</h2>
|
||||||
<KeplrConnect onConnect={handleConnect} />
|
<KeplrConnect onConnect={handleConnect} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="mb-8 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)', opacity: walletAddress ? '1' : '0.6' }}>
|
||||||
<h2 className="text-lg font-semibold mb-4">2. Enter URL to Deploy</h2>
|
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||||
|
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||||
|
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>2</span>
|
||||||
|
Enter URL to Deploy
|
||||||
|
</h2>
|
||||||
<URLForm
|
<URLForm
|
||||||
onSubmit={handleUrlSubmit}
|
onSubmit={handleUrlSubmit}
|
||||||
disabled={!walletAddress || status === 'verifying' || status === 'creating'}
|
disabled={!walletAddress || status === 'verifying' || status === 'creating'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{status !== 'idle' && (
|
||||||
|
<div className="p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
|
||||||
|
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||||
|
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||||
|
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>3</span>
|
||||||
|
Deployment Status
|
||||||
|
</h2>
|
||||||
<StatusDisplay
|
<StatusDisplay
|
||||||
status={status}
|
status={status}
|
||||||
txHash={txHash || undefined}
|
txHash={txHash || undefined}
|
||||||
recordId={recordId || undefined}
|
recordId={recordId || undefined}
|
||||||
appRecordId={appRecordId || undefined}
|
appRecordId={appRecordId || undefined}
|
||||||
lrn={lrn || undefined}
|
lrn={lrn || undefined}
|
||||||
|
dns={dns || undefined}
|
||||||
|
appName={appName || undefined}
|
||||||
|
repoUrl={repoUrl || undefined}
|
||||||
|
commitHash={commitHash || undefined}
|
||||||
|
shortCommitHash={shortCommitHash || undefined}
|
||||||
error={error || undefined}
|
error={error || undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{showPaymentModal && walletAddress && url && (
|
{showPaymentModal && walletAddress && url && (
|
||||||
<PaymentModal
|
<PaymentModal
|
||||||
|
38
src/components/ErrorBoundary.tsx
Normal file
38
src/components/ErrorBoundary.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
// This component will suppress Next.js error overlay in development
|
||||||
|
export default function ErrorBoundary() {
|
||||||
|
useEffect(() => {
|
||||||
|
// This targets the Next.js error overlay in development
|
||||||
|
const errorOverlay = document.querySelector('nextjs-portal');
|
||||||
|
if (errorOverlay) {
|
||||||
|
errorOverlay.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply style to hide the error icon in the bottom right
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
/* Hide Next.js error overlay and icon */
|
||||||
|
nextjs-portal {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specifically target the error popup button */
|
||||||
|
[data-nextjs-dialog-overlay],
|
||||||
|
[data-nextjs-toast],
|
||||||
|
[data-nextjs-codeframe] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
return () => {
|
||||||
|
document.head.removeChild(style);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
10
src/components/ErrorBoundaryWrapper.tsx
Normal file
10
src/components/ErrorBoundaryWrapper.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
// Dynamically import the error boundary without SSR
|
||||||
|
const ErrorBoundary = dynamic(() => import('./ErrorBoundary'), { ssr: false });
|
||||||
|
|
||||||
|
export default function ErrorBoundaryWrapper() {
|
||||||
|
return <ErrorBoundary />;
|
||||||
|
}
|
@ -36,19 +36,37 @@ export default function KeplrConnect({ onConnect }: KeplrConnectProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center p-4 bg-gray-100 rounded-lg">
|
<div className="flex flex-col items-center p-4 rounded-lg">
|
||||||
{address ? (
|
{address ? (
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center w-full">
|
||||||
<p className="mb-2 text-green-600 font-medium">Connected</p>
|
<div className="flex items-center mb-2">
|
||||||
<p className="text-sm font-mono truncate max-w-xs">{address}</p>
|
<span className="w-3 h-3 rounded-full mr-2" style={{ backgroundColor: 'var(--success)' }}></span>
|
||||||
|
<p className="font-medium" style={{ color: 'var(--success)' }}>Connected</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-full p-3 rounded-md" style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}>
|
||||||
|
<p className="text-sm font-mono break-all text-center">{address}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={handleConnect}
|
onClick={handleConnect}
|
||||||
disabled={connecting}
|
disabled={connecting}
|
||||||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-blue-400"
|
className="px-6 py-3 rounded-md w-full sm:w-auto transition-colors"
|
||||||
|
style={{
|
||||||
|
backgroundColor: connecting ? 'var(--muted)' : 'var(--primary)',
|
||||||
|
color: 'var(--primary-foreground)',
|
||||||
|
opacity: connecting ? '0.8' : '1',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
{connecting && (
|
||||||
|
<svg className="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
{connecting ? 'Connecting...' : 'Connect Keplr Wallet'}
|
{connecting ? 'Connecting...' : 'Connect Keplr Wallet'}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@ export default function PaymentModal({
|
|||||||
url,
|
url,
|
||||||
onPaymentComplete,
|
onPaymentComplete,
|
||||||
}: PaymentModalProps) {
|
}: PaymentModalProps) {
|
||||||
const [amount, setAmount] = useState('1');
|
const [amount, setAmount] = useState('0.001');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
@ -45,54 +45,89 @@ export default function PaymentModal({
|
|||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4">
|
<div className="fixed inset-0 flex items-center justify-center p-4 z-50" style={{ background: 'rgba(15, 23, 42, 0.75)' }}>
|
||||||
<div className="bg-white rounded-lg p-6 max-w-md w-full">
|
<div className="max-w-md w-full rounded-xl shadow-xl animate-appear"
|
||||||
<h2 className="text-xl font-semibold mb-4">Complete Payment</h2>
|
style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}>
|
||||||
|
<div className="p-6 border-b" style={{ borderColor: 'var(--card-border)' }}>
|
||||||
<div className="mb-4">
|
<h2 className="text-xl font-semibold" style={{ color: 'var(--foreground)' }}>Complete Payment</h2>
|
||||||
<p className="text-sm text-gray-600 mb-2">URL to be deployed:</p>
|
|
||||||
<p className="text-sm font-mono bg-gray-100 p-2 rounded">{url}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="p-6 space-y-6">
|
||||||
<p className="text-sm text-gray-600 mb-2">Recipient Address:</p>
|
<div>
|
||||||
<p className="text-sm font-mono bg-gray-100 p-2 rounded truncate">{recipientAddress}</p>
|
<p className="text-sm mb-2 font-medium" style={{ color: 'var(--muted)' }}>URL to be deployed:</p>
|
||||||
|
<div className="p-3 rounded-md break-all" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}>
|
||||||
|
<code className="text-sm font-mono">{url}</code>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-6">
|
<div>
|
||||||
<label htmlFor="amount" className="block text-sm font-medium text-gray-700 mb-1">
|
<p className="text-sm mb-2 font-medium" style={{ color: 'var(--muted)' }}>Recipient Address:</p>
|
||||||
|
<div className="p-3 rounded-md overflow-hidden" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}>
|
||||||
|
<code className="text-sm font-mono break-all block">{recipientAddress}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="amount" className="block text-sm font-medium mb-2" style={{ color: 'var(--foreground)' }}>
|
||||||
Amount (ATOM)
|
Amount (ATOM)
|
||||||
</label>
|
</label>
|
||||||
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
id="amount"
|
id="amount"
|
||||||
type="number"
|
type="number"
|
||||||
min="0.1"
|
min="0.001"
|
||||||
step="0.1"
|
step="0.001"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={(e) => setAmount(e.target.value)}
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
className="w-full p-2 border border-gray-300 rounded-md"
|
className="w-full p-3 pr-12 rounded-md"
|
||||||
|
style={{
|
||||||
|
background: 'var(--card-bg)',
|
||||||
|
border: '1px solid var(--input-border)',
|
||||||
|
color: 'var(--foreground)'
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||||
|
<span className="text-sm font-medium" style={{ color: 'var(--muted)' }}>ATOM</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-4 p-2 bg-red-100 text-red-700 rounded-md text-sm">
|
<div className="p-3 rounded-md text-sm" style={{ backgroundColor: 'var(--error-light)', color: 'var(--error)' }}>
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-4">
|
<div className="p-6 flex justify-end space-x-4 border-t" style={{ borderColor: 'var(--card-border)' }}>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-100"
|
className="px-4 py-2 rounded-md transition-colors"
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--input-border)',
|
||||||
|
color: 'var(--foreground)',
|
||||||
|
opacity: loading ? '0.5' : '1'
|
||||||
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handlePayment}
|
onClick={handlePayment}
|
||||||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-blue-400"
|
className="px-5 py-2 rounded-md flex items-center transition-colors"
|
||||||
|
style={{
|
||||||
|
backgroundColor: loading ? 'var(--muted)' : 'var(--primary)',
|
||||||
|
color: 'var(--primary-foreground)',
|
||||||
|
opacity: loading ? '0.8' : '1'
|
||||||
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
{loading && (
|
||||||
|
<svg className="animate-spin -ml-1 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
{loading ? 'Processing...' : 'Pay with Keplr'}
|
{loading ? 'Processing...' : 'Pay with Keplr'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,11 @@ interface StatusDisplayProps {
|
|||||||
recordId?: string;
|
recordId?: string;
|
||||||
appRecordId?: string;
|
appRecordId?: string;
|
||||||
lrn?: string;
|
lrn?: string;
|
||||||
|
dns?: string;
|
||||||
|
appName?: string;
|
||||||
|
repoUrl?: string;
|
||||||
|
commitHash?: string;
|
||||||
|
shortCommitHash?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,80 +20,128 @@ export default function StatusDisplay({
|
|||||||
recordId,
|
recordId,
|
||||||
appRecordId,
|
appRecordId,
|
||||||
lrn,
|
lrn,
|
||||||
|
dns,
|
||||||
|
appName,
|
||||||
|
repoUrl,
|
||||||
|
commitHash,
|
||||||
|
shortCommitHash,
|
||||||
error,
|
error,
|
||||||
}: StatusDisplayProps) {
|
}: StatusDisplayProps) {
|
||||||
if (status === 'idle') return null;
|
if (status === 'idle') return null;
|
||||||
|
|
||||||
|
const StatusBadge = ({ type }: { type: 'verifying' | 'creating' | 'success' | 'error' }) => {
|
||||||
|
const getBadgeStyles = () => {
|
||||||
|
switch (type) {
|
||||||
|
case 'verifying':
|
||||||
|
case 'creating':
|
||||||
|
return {
|
||||||
|
bg: 'var(--warning-light)',
|
||||||
|
color: 'var(--warning)',
|
||||||
|
text: type === 'verifying' ? 'Verifying' : 'Creating Record'
|
||||||
|
};
|
||||||
|
case 'success':
|
||||||
|
return {
|
||||||
|
bg: 'var(--success-light)',
|
||||||
|
color: 'var(--success)',
|
||||||
|
text: 'Success'
|
||||||
|
};
|
||||||
|
case 'error':
|
||||||
|
return {
|
||||||
|
bg: 'var(--error-light)',
|
||||||
|
color: 'var(--error)',
|
||||||
|
text: 'Error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = getBadgeStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-8 p-4 bg-gray-100 rounded-lg">
|
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold"
|
||||||
<h3 className="text-lg font-semibold mb-2">Status</h3>
|
style={{ backgroundColor: styles.bg, color: styles.color }}>
|
||||||
|
{styles.text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
{status === 'verifying' && (
|
const InfoItem = ({ label, value }: { label: string, value: string }) => (
|
||||||
<div className="flex items-center text-yellow-600">
|
<div className="mb-3 border rounded-md overflow-hidden" style={{ borderColor: 'var(--card-border)' }}>
|
||||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
<div className="px-3 py-2 text-xs font-semibold" style={{ background: 'var(--muted-light)' }}>
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
{label}
|
||||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
Verifying transaction...
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="px-3 py-2 text-sm font-mono break-all bg-opacity-50" style={{ background: 'var(--card-bg)' }}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
{status === 'creating' && (
|
return (
|
||||||
<div className="flex items-center text-yellow-600">
|
<div>
|
||||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
{(status === 'verifying' || status === 'creating') && (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<StatusBadge type={status} />
|
||||||
|
<div className="ml-3 flex items-center">
|
||||||
|
<svg className="animate-spin mr-2 h-4 w-4" style={{ color: 'var(--warning)' }} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Creating Laconic Registry record...
|
<span style={{ color: 'var(--warning)' }}>
|
||||||
|
{status === 'verifying' ? 'Verifying transaction...' : 'Creating Laconic Registry record...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{status === 'success' && (
|
{status === 'success' && (
|
||||||
<div className="text-green-600">
|
<div className="flex items-center">
|
||||||
<p className="font-semibold mb-2">Successfully deployed!</p>
|
<StatusBadge type="success" />
|
||||||
|
<span className="ml-3" style={{ color: 'var(--success)' }}>Successfully deployed!</span>
|
||||||
{txHash && (
|
|
||||||
<div className="mb-2">
|
|
||||||
<p className="text-sm font-medium text-gray-700">Transaction Hash:</p>
|
|
||||||
<p className="text-sm font-mono break-all">{txHash}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{appRecordId && (
|
|
||||||
<div className="mb-2">
|
|
||||||
<p className="text-sm font-medium text-gray-700">Application Record ID:</p>
|
|
||||||
<p className="text-sm font-mono break-all">{appRecordId}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{recordId && (
|
|
||||||
<div className="mb-2">
|
|
||||||
<p className="text-sm font-medium text-gray-700">Deployment Request Record ID:</p>
|
|
||||||
<p className="text-sm font-mono break-all">{recordId}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{lrn && (
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium text-gray-700">Laconic Resource Name (LRN):</p>
|
|
||||||
<p className="text-sm font-mono break-all">{lrn}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{status === 'error' && (
|
{status === 'error' && (
|
||||||
<div className="text-red-600">
|
<div className="flex items-center">
|
||||||
<p className="font-semibold mb-2">Error</p>
|
<StatusBadge type="error" />
|
||||||
<p className="text-sm">{error || 'An unknown error occurred'}</p>
|
<span className="ml-3" style={{ color: 'var(--error)' }}>Deployment Failed</span>
|
||||||
|
|
||||||
{txHash && (
|
|
||||||
<div className="mt-2">
|
|
||||||
<p className="text-sm font-medium text-gray-700">Transaction Hash:</p>
|
|
||||||
<p className="text-sm font-mono break-all">{txHash}</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{status === 'success' && (
|
||||||
|
<div className="mt-4">
|
||||||
|
{appName && (
|
||||||
|
<div className="mb-6 p-4 rounded-md" style={{ backgroundColor: 'var(--success-light)', color: 'var(--success)' }}>
|
||||||
|
<h3 className="font-semibold mb-2 text-lg">
|
||||||
|
Successfully deployed
|
||||||
|
{appName && <span> {appName}</span>}
|
||||||
|
{dns && <span> as {dns}</span>}
|
||||||
|
</h3>
|
||||||
|
{repoUrl && (
|
||||||
|
<p className="mb-1 text-sm">
|
||||||
|
<span className="font-medium">Repository:</span> {repoUrl}
|
||||||
|
{shortCommitHash && <span> @ {shortCommitHash}</span>}
|
||||||
|
{(!shortCommitHash && commitHash) && <span> @ {commitHash.substring(0, 7)}</span>}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{txHash && <InfoItem label="Transaction Hash" value={txHash} />}
|
||||||
|
{appRecordId && <InfoItem label="Application Record ID" value={appRecordId} />}
|
||||||
|
{recordId && <InfoItem label="Deployment Request Record ID" value={recordId} />}
|
||||||
|
{lrn && <InfoItem label="Laconic Resource Name (LRN)" value={lrn} />}
|
||||||
|
{dns && <InfoItem label="Deployment DNS" value={dns} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === 'error' && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="p-3 rounded-md mb-4" style={{ backgroundColor: 'var(--error-light)', color: 'var(--error)' }}>
|
||||||
|
{error || 'An unknown error occurred'}
|
||||||
|
</div>
|
||||||
|
{txHash && <InfoItem label="Transaction Hash" value={txHash} />}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -30,29 +30,54 @@ export default function URLForm({ onSubmit, disabled }: URLFormProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="w-full space-y-4">
|
<form onSubmit={handleSubmit} className="w-full space-y-6">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<label htmlFor="url" className="mb-2 text-sm font-medium text-gray-700">
|
<label htmlFor="url" className="mb-2 text-sm font-semibold" style={{ color: 'var(--foreground)' }}>
|
||||||
URL to Deploy
|
URL to Deploy
|
||||||
</label>
|
</label>
|
||||||
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
id="url"
|
id="url"
|
||||||
type="text"
|
type="text"
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
placeholder="https://example.com"
|
placeholder="https://example.com"
|
||||||
className="p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full p-3 rounded-md transition-colors"
|
||||||
|
style={{
|
||||||
|
background: 'var(--card-bg)',
|
||||||
|
border: '1px solid var(--input-border)',
|
||||||
|
color: 'var(--foreground)',
|
||||||
|
opacity: disabled ? '0.6' : '1'
|
||||||
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
{error && <p className="mt-1 text-sm text-red-600">{error}</p>}
|
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 opacity-60">
|
||||||
|
{url && (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
|
||||||
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{error && (
|
||||||
|
<p className="mt-2 text-sm font-medium px-3 py-2 rounded-md" style={{ color: 'var(--error)', background: 'var(--error-light)' }}>
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={disabled || !url}
|
disabled={disabled || !url}
|
||||||
className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-blue-400"
|
className="w-full px-6 py-3 rounded-md transition-colors"
|
||||||
|
style={{
|
||||||
|
backgroundColor: (disabled || !url) ? 'var(--muted)' : 'var(--primary)',
|
||||||
|
color: 'var(--primary-foreground)',
|
||||||
|
opacity: (disabled || !url) ? '0.7' : '1',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Deploy URL
|
{disabled ? 'Connect Wallet First' : 'Deploy URL'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
@ -25,6 +25,11 @@ export const createApplicationDeploymentRequest = async (
|
|||||||
id: result.id,
|
id: result.id,
|
||||||
applicationRecordId: result.applicationRecordId,
|
applicationRecordId: result.applicationRecordId,
|
||||||
lrn: result.lrn,
|
lrn: result.lrn,
|
||||||
|
dns: result.dns,
|
||||||
|
appName: result.appName,
|
||||||
|
repoUrl: result.repoUrl,
|
||||||
|
commitHash: result.commitHash,
|
||||||
|
shortCommitHash: result.shortCommitHash,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -44,6 +44,11 @@ export interface CreateRecordResponse {
|
|||||||
id: string;
|
id: string;
|
||||||
applicationRecordId?: string;
|
applicationRecordId?: string;
|
||||||
lrn?: string;
|
lrn?: string;
|
||||||
|
dns?: string;
|
||||||
|
appName?: string;
|
||||||
|
repoUrl?: string;
|
||||||
|
commitHash?: string;
|
||||||
|
shortCommitHash?: string;
|
||||||
status: 'success' | 'error';
|
status: 'success' | 'error';
|
||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user