Compare commits
9
Commits
main
...
zach/build-fixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f54737a43f | ||
|
|
c5fbf0cd8e | ||
|
|
c34b443b69 | ||
|
|
b77bf4dac4 | ||
|
|
76bca0e525 | ||
|
|
fd7b298e55 | ||
|
|
d10ed5ed11 | ||
|
|
8a87469fcf | ||
|
|
61f5683b8b |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"enableAllProjectMcpServers": false
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
"siwe": "^3.0.0",
|
||||
"toml": "^3.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typeorm": "^0.3.19",
|
||||
"typeorm": "^0.3.26",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -60,7 +60,7 @@
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.17.10",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"better-sqlite3": "^9.2.2",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"prettier": "^3.1.1",
|
||||
"workspace": "^0.0.1-preview.1"
|
||||
|
||||
@@ -63,14 +63,11 @@ export default async function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
// NOTE: We're keeping the token approach for now, but aware it's not working
|
||||
const authToken = githubAccount.accessToken;
|
||||
|
||||
// Try using GitHub token
|
||||
let octokit;
|
||||
try {
|
||||
octokit = new Octokit({
|
||||
auth: authToken || process.env.GITHUB_TOKEN
|
||||
auth: process.env.GITHUB_TOKEN
|
||||
});
|
||||
|
||||
// Test with a simple request
|
||||
@@ -145,7 +142,7 @@ export default async function Page() {
|
||||
Failed to access GitHub API
|
||||
</div>
|
||||
<div className="text-red-500 mb-4">
|
||||
{authError.message}
|
||||
{authError instanceof Error ? authError.message : 'An error occurred'}
|
||||
</div>
|
||||
<div className="text-muted-foreground text-center max-w-md mb-6">
|
||||
<p>This issue may be related to how Clerk is managing the GitHub token.</p>
|
||||
@@ -182,7 +179,7 @@ export default async function Page() {
|
||||
Failed to authenticate with GitHub
|
||||
</div>
|
||||
<div className="text-red-500">
|
||||
{error.message}
|
||||
{error instanceof Error ? error.message : 'Unknown error occurred'}
|
||||
</div>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
|
||||
+226
-84
@@ -1,15 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { X } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useOnboarding } from '@/components/onboarding/useOnboarding'
|
||||
import { ConnectStep } from '@/components/onboarding/connect-step/connect-step'
|
||||
import { LaconicMark } from '@/components/assets/laconic-mark'
|
||||
import { ConfigureStep } from '@/components/onboarding/configure-step/configure-step'
|
||||
import { ConnectStep } from '@/components/onboarding/connect-step/connect-step'
|
||||
import { DeployStep } from '@/components/onboarding/deploy-step/deploy-step'
|
||||
import { SuccessStep } from '@/components/onboarding/success-step/success-step'
|
||||
import { LaconicMark } from '@/components/assets/laconic-mark'
|
||||
import { useOnboarding } from '@/components/onboarding/useOnboarding'
|
||||
import { X } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Parent component for the onboarding flow
|
||||
@@ -19,26 +19,26 @@ export default function CreateProjectFlow() {
|
||||
const router = useRouter()
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
|
||||
const { currentStep, setCurrentStep, resetOnboarding } = useOnboarding()
|
||||
|
||||
|
||||
// Handle hydration mismatch by waiting for mount
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
|
||||
// Reset onboarding state when the component unmounts (optional)
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Optional cleanup actions
|
||||
}
|
||||
}, [resetOnboarding])
|
||||
|
||||
|
||||
// Handle closing the modal
|
||||
const handleClose = () => {
|
||||
router.push('/projects')
|
||||
}
|
||||
|
||||
|
||||
// Navigate directly to a specific step
|
||||
const navigateToStep = (step: 'connect' | 'configure' | 'deploy') => {
|
||||
setCurrentStep(step)
|
||||
@@ -48,102 +48,216 @@ export default function CreateProjectFlow() {
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
// Determine if dark mode is active
|
||||
const isDarkMode = resolvedTheme === 'dark'
|
||||
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
|
||||
{/* Fixed dimensions modal container */}
|
||||
<div className={`${isDarkMode ? 'bg-black' : 'bg-white'} rounded-xl overflow-hidden flex shadow-2xl w-[1000px] h-[620px]`}>
|
||||
<div
|
||||
className={`${isDarkMode ? 'bg-black' : 'bg-white'} rounded-xl overflow-hidden flex shadow-2xl w-[1000px] h-[620px]`}
|
||||
>
|
||||
{/* Left sidebar with fixed width */}
|
||||
<div className={`w-[280px] min-w-[280px] ${isDarkMode ? 'bg-zinc-900' : 'bg-zinc-50'} p-8 relative overflow-hidden border-r ${isDarkMode ? 'border-zinc-800' : 'border-zinc-200'}`}>
|
||||
<div
|
||||
className={`w-[280px] min-w-[280px] ${isDarkMode ? 'bg-zinc-900' : 'bg-zinc-50'} p-8 relative overflow-hidden border-r ${isDarkMode ? 'border-zinc-800' : 'border-zinc-200'}`}
|
||||
>
|
||||
{/* Laconic logo */}
|
||||
<div className="flex items-center gap-2 mb-12">
|
||||
<LaconicMark className="h-8 w-8" />
|
||||
<span className={`${isDarkMode ? 'text-white' : 'text-zinc-900'} text-xl font-bold`}>LACONIC</span>
|
||||
<span
|
||||
className={`${isDarkMode ? 'text-white' : 'text-zinc-900'} text-xl font-bold`}
|
||||
>
|
||||
LACONIC
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Steps - clickable */}
|
||||
<div className="space-y-6">
|
||||
{/* Connect step */}
|
||||
<button
|
||||
<button
|
||||
className="flex w-full text-left"
|
||||
onClick={() => navigateToStep('connect')}
|
||||
>
|
||||
<div className="mr-4">
|
||||
<div className={`w-10 h-10 rounded-lg ${currentStep === 'connect'
|
||||
? (isDarkMode ? 'bg-white' : 'bg-black')
|
||||
: (isDarkMode ? 'bg-zinc-800' : 'bg-zinc-200')
|
||||
} flex items-center justify-center`}>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" className={currentStep === 'connect'
|
||||
? (isDarkMode ? 'text-black' : 'text-white')
|
||||
: (isDarkMode ? 'text-zinc-400' : 'text-zinc-600')
|
||||
}>
|
||||
<div
|
||||
className={`w-10 h-10 rounded-lg ${
|
||||
currentStep === 'connect'
|
||||
? isDarkMode
|
||||
? 'bg-white'
|
||||
: 'bg-black'
|
||||
: isDarkMode
|
||||
? 'bg-zinc-800'
|
||||
: 'bg-zinc-200'
|
||||
} flex items-center justify-center`}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={
|
||||
currentStep === 'connect'
|
||||
? isDarkMode
|
||||
? 'text-black'
|
||||
: 'text-white'
|
||||
: isDarkMode
|
||||
? 'text-zinc-400'
|
||||
: 'text-zinc-600'
|
||||
}
|
||||
>
|
||||
<path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
|
||||
<line x1="8" y1="12" x2="16" y2="12"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div className={`w-px h-10 ${isDarkMode ? 'bg-zinc-800' : 'bg-zinc-200'} mx-auto mt-2`}></div>
|
||||
<div
|
||||
className={`w-px h-10 ${isDarkMode ? 'bg-zinc-800' : 'bg-zinc-200'} mx-auto mt-2`}
|
||||
></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className={`font-medium text-base ${currentStep === 'connect'
|
||||
? (isDarkMode ? 'text-white' : 'text-zinc-900')
|
||||
: (isDarkMode ? 'text-zinc-400' : 'text-zinc-600')
|
||||
}`}>Connect</h3>
|
||||
<p className={`text-sm ${currentStep === 'connect'
|
||||
? (isDarkMode ? 'text-zinc-300' : 'text-zinc-700')
|
||||
: (isDarkMode ? 'text-zinc-500' : 'text-zinc-500')
|
||||
}`}>Connect and import a GitHub repo</p>
|
||||
<h3
|
||||
className={`font-medium text-base ${
|
||||
currentStep === 'connect'
|
||||
? isDarkMode
|
||||
? 'text-white'
|
||||
: 'text-zinc-900'
|
||||
: isDarkMode
|
||||
? 'text-zinc-400'
|
||||
: 'text-zinc-600'
|
||||
}`}
|
||||
>
|
||||
Connect
|
||||
</h3>
|
||||
<p
|
||||
className={`text-sm ${
|
||||
currentStep === 'connect'
|
||||
? isDarkMode
|
||||
? 'text-zinc-300'
|
||||
: 'text-zinc-700'
|
||||
: isDarkMode
|
||||
? 'text-zinc-500'
|
||||
: 'text-zinc-500'
|
||||
}`}
|
||||
>
|
||||
Connect and import a GitHub repo
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
||||
{/* Configure step */}
|
||||
<button
|
||||
<button
|
||||
className="flex w-full text-left"
|
||||
onClick={() => navigateToStep('configure')}
|
||||
>
|
||||
<div className="mr-4">
|
||||
<div className={`w-10 h-10 rounded-lg ${currentStep === 'configure'
|
||||
? (isDarkMode ? 'bg-white' : 'bg-black')
|
||||
: (isDarkMode ? 'bg-zinc-800' : 'bg-zinc-200')
|
||||
} flex items-center justify-center`}>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" className={currentStep === 'configure'
|
||||
? (isDarkMode ? 'text-black' : 'text-white')
|
||||
: (isDarkMode ? 'text-zinc-400' : 'text-zinc-600')
|
||||
}>
|
||||
<div
|
||||
className={`w-10 h-10 rounded-lg ${
|
||||
currentStep === 'configure'
|
||||
? isDarkMode
|
||||
? 'bg-white'
|
||||
: 'bg-black'
|
||||
: isDarkMode
|
||||
? 'bg-zinc-800'
|
||||
: 'bg-zinc-200'
|
||||
} flex items-center justify-center`}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={
|
||||
currentStep === 'configure'
|
||||
? isDarkMode
|
||||
? 'text-black'
|
||||
: 'text-white'
|
||||
: isDarkMode
|
||||
? 'text-zinc-400'
|
||||
: 'text-zinc-600'
|
||||
}
|
||||
>
|
||||
<path d="M12 20h9"></path>
|
||||
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div className={`w-px h-10 ${isDarkMode ? 'bg-zinc-800' : 'bg-zinc-200'} mx-auto mt-2`}></div>
|
||||
<div
|
||||
className={`w-px h-10 ${isDarkMode ? 'bg-zinc-800' : 'bg-zinc-200'} mx-auto mt-2`}
|
||||
></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className={`font-medium text-base ${currentStep === 'configure'
|
||||
? (isDarkMode ? 'text-white' : 'text-zinc-900')
|
||||
: (isDarkMode ? 'text-zinc-400' : 'text-zinc-600')
|
||||
}`}>Configure</h3>
|
||||
<p className={`text-sm ${currentStep === 'configure'
|
||||
? (isDarkMode ? 'text-zinc-300' : 'text-zinc-700')
|
||||
: (isDarkMode ? 'text-zinc-500' : 'text-zinc-500')
|
||||
}`}>Define the deployment type</p>
|
||||
<h3
|
||||
className={`font-medium text-base ${
|
||||
currentStep === 'configure'
|
||||
? isDarkMode
|
||||
? 'text-white'
|
||||
: 'text-zinc-900'
|
||||
: isDarkMode
|
||||
? 'text-zinc-400'
|
||||
: 'text-zinc-600'
|
||||
}`}
|
||||
>
|
||||
Configure
|
||||
</h3>
|
||||
<p
|
||||
className={`text-sm ${
|
||||
currentStep === 'configure'
|
||||
? isDarkMode
|
||||
? 'text-zinc-300'
|
||||
: 'text-zinc-700'
|
||||
: isDarkMode
|
||||
? 'text-zinc-500'
|
||||
: 'text-zinc-500'
|
||||
}`}
|
||||
>
|
||||
Define the deployment type
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
||||
{/* Deploy step */}
|
||||
<button
|
||||
<button
|
||||
className="flex w-full text-left"
|
||||
onClick={() => navigateToStep('deploy')}
|
||||
>
|
||||
<div className="mr-4">
|
||||
<div className={`w-10 h-10 rounded-lg ${currentStep === 'deploy' || currentStep === 'success'
|
||||
? (isDarkMode ? 'bg-white' : 'bg-black')
|
||||
: (isDarkMode ? 'bg-zinc-800' : 'bg-zinc-200')
|
||||
} flex items-center justify-center`}>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" className={currentStep === 'deploy' || currentStep === 'success'
|
||||
? (isDarkMode ? 'text-black' : 'text-white')
|
||||
: (isDarkMode ? 'text-zinc-400' : 'text-zinc-600')
|
||||
}>
|
||||
<div
|
||||
className={`w-10 h-10 rounded-lg ${
|
||||
currentStep === 'deploy' || currentStep === 'success'
|
||||
? isDarkMode
|
||||
? 'bg-white'
|
||||
: 'bg-black'
|
||||
: isDarkMode
|
||||
? 'bg-zinc-800'
|
||||
: 'bg-zinc-200'
|
||||
} flex items-center justify-center`}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={
|
||||
currentStep === 'deploy' || currentStep === 'success'
|
||||
? isDarkMode
|
||||
? 'text-black'
|
||||
: 'text-white'
|
||||
: isDarkMode
|
||||
? 'text-zinc-400'
|
||||
: 'text-zinc-600'
|
||||
}
|
||||
>
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
<polyline points="7.5 4.21 12 6.81 16.5 4.21"></polyline>
|
||||
<polyline points="7.5 19.79 7.5 14.6 3 12"></polyline>
|
||||
@@ -154,34 +268,56 @@ export default function CreateProjectFlow() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className={`font-medium text-base ${currentStep === 'deploy' || currentStep === 'success'
|
||||
? (isDarkMode ? 'text-white' : 'text-zinc-900')
|
||||
: (isDarkMode ? 'text-zinc-400' : 'text-zinc-600')
|
||||
}`}>Deploy</h3>
|
||||
<p className={`text-sm ${currentStep === 'deploy' || currentStep === 'success'
|
||||
? (isDarkMode ? 'text-zinc-300' : 'text-zinc-700')
|
||||
: (isDarkMode ? 'text-zinc-500' : 'text-zinc-500')
|
||||
}`}>Review and confirm deployment</p>
|
||||
<h3
|
||||
className={`font-medium text-base ${
|
||||
currentStep === 'deploy' || currentStep === 'success'
|
||||
? isDarkMode
|
||||
? 'text-white'
|
||||
: 'text-zinc-900'
|
||||
: isDarkMode
|
||||
? 'text-zinc-400'
|
||||
: 'text-zinc-600'
|
||||
}`}
|
||||
>
|
||||
Deploy
|
||||
</h3>
|
||||
<p
|
||||
className={`text-sm ${
|
||||
currentStep === 'deploy' || currentStep === 'success'
|
||||
? isDarkMode
|
||||
? 'text-zinc-300'
|
||||
: 'text-zinc-700'
|
||||
: isDarkMode
|
||||
? 'text-zinc-500'
|
||||
: 'text-zinc-500'
|
||||
}`}
|
||||
>
|
||||
Review and confirm deployment
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Laconic mark (larger, bottom left) */}
|
||||
<div className="absolute -bottom-2 -left-2 opacity-10">
|
||||
<LaconicMark className={`w-40 h-40 ${isDarkMode ? 'text-zinc-300' : 'text-zinc-700'}`} />
|
||||
<LaconicMark
|
||||
className={`w-40 h-40 ${isDarkMode ? 'text-zinc-300' : 'text-zinc-700'}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Main content with fixed dimensions and scrolling */}
|
||||
<div className={`flex-1 ${isDarkMode ? 'bg-black' : 'bg-white'} relative`}>
|
||||
<div
|
||||
className={`flex-1 ${isDarkMode ? 'bg-black' : 'bg-white'} relative`}
|
||||
>
|
||||
{/* Close button */}
|
||||
<button
|
||||
<button
|
||||
className={`absolute top-4 right-4 ${isDarkMode ? 'text-zinc-400 hover:text-white' : 'text-zinc-600 hover:text-zinc-900'} z-10`}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X size={24} />
|
||||
</button>
|
||||
|
||||
|
||||
{/* Scrollable content container */}
|
||||
<div className="w-full h-full overflow-y-auto">
|
||||
{currentStep === 'connect' && <ConnectStep />}
|
||||
@@ -189,15 +325,21 @@ export default function CreateProjectFlow() {
|
||||
{currentStep === 'deploy' && <DeployStep />}
|
||||
{currentStep === 'success' && <SuccessStep />}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Progress indicator */}
|
||||
<div className="absolute bottom-6 left-0 right-0 flex justify-center gap-3">
|
||||
<div className={`w-12 h-1 rounded-full ${currentStep === 'connect' ? 'bg-blue-600' : (isDarkMode ? 'bg-zinc-700' : 'bg-zinc-300')}`}></div>
|
||||
<div className={`w-12 h-1 rounded-full ${currentStep === 'configure' ? 'bg-blue-600' : (isDarkMode ? 'bg-zinc-700' : 'bg-zinc-300')}`}></div>
|
||||
<div className={`w-12 h-1 rounded-full ${currentStep === 'deploy' || currentStep === 'success' ? 'bg-blue-600' : (isDarkMode ? 'bg-zinc-700' : 'bg-zinc-300')}`}></div>
|
||||
<div
|
||||
className={`w-12 h-1 rounded-full ${currentStep === 'connect' ? 'bg-blue-600' : isDarkMode ? 'bg-zinc-700' : 'bg-zinc-300'}`}
|
||||
></div>
|
||||
<div
|
||||
className={`w-12 h-1 rounded-full ${currentStep === 'configure' ? 'bg-blue-600' : isDarkMode ? 'bg-zinc-700' : 'bg-zinc-300'}`}
|
||||
></div>
|
||||
<div
|
||||
className={`w-12 h-1 rounded-full ${currentStep === 'deploy' || currentStep === 'success' ? 'bg-blue-600' : isDarkMode ? 'bg-zinc-700' : 'bg-zinc-300'}`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-14
@@ -30,7 +30,7 @@ export default function DeploymentsPage() {
|
||||
|
||||
// State for deployment logs modal
|
||||
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
||||
const [selectedDeploymentId, setSelectedDeploymentId] = useState<string | null>(null);
|
||||
const [, setSelectedDeploymentId] = useState<string | null>(null);
|
||||
const [deploymentLogs, setDeploymentLogs] = useState<string>('');
|
||||
|
||||
// Create a default deployment
|
||||
@@ -48,19 +48,6 @@ export default function DeploymentsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const secondDeployment: Deployment = {
|
||||
id: 'previous',
|
||||
branch: 'feature/new-ui',
|
||||
status: 'COMPLETED',
|
||||
isCurrent: false,
|
||||
createdAt: Date.now() - 3 * 24 * 60 * 60 * 1000, // 3 days ago
|
||||
applicationDeploymentRecordData: {
|
||||
url: repoData ? `https://dev.${repoData.name.toLowerCase()}.example.com` : 'https://dev.example.com'
|
||||
},
|
||||
createdBy: {
|
||||
name: repoData?.owner?.login || 'username'
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize with empty data for testing the empty state
|
||||
// Comment this out to see the mock deployments
|
||||
@@ -79,7 +66,9 @@ export default function DeploymentsPage() {
|
||||
{
|
||||
id: '1',
|
||||
name: repoData ? `${repoData.name.toLowerCase()}.example.com` : 'example.com',
|
||||
branch: 'main', // Add missing branch
|
||||
status: 'ACTIVE',
|
||||
createdAt: Date.now(), // Add missing createdAt
|
||||
isCustom: false
|
||||
}
|
||||
];
|
||||
|
||||
+2
-7
@@ -3,12 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import { LoadingOverlay } from "@/components/foundation/loading/loading-overlay";
|
||||
import { PlusIcon, ChevronDownIcon, ChevronUpIcon, PencilIcon, TrashIcon } from "lucide-react";
|
||||
|
||||
interface EnvVarItem {
|
||||
key: string;
|
||||
value: string;
|
||||
isEditing?: boolean;
|
||||
}
|
||||
import type { EnvVarItem } from '@/types'
|
||||
|
||||
interface EnvGroupProps {
|
||||
title: string;
|
||||
@@ -204,7 +199,7 @@ export default function EnvVarsPage() {
|
||||
/>
|
||||
<button
|
||||
className="p-2 hover:bg-accent hover:text-accent-foreground rounded-md text-foreground transition-colors"
|
||||
onClick={() => updateVariable(env, index, variable.key, variable.value)}
|
||||
onClick={() => updateVariable(env, index, variable.key || '', variable.value || '')}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
+2
-4
@@ -278,11 +278,9 @@ export default function ProjectSettingsPage({ project, onProjectUpdated }: Proje
|
||||
Select account
|
||||
</Label>
|
||||
<Dropdown
|
||||
label="Select"
|
||||
placeholder="Select"
|
||||
options={accountOptions}
|
||||
selectedValue={selectedAccount}
|
||||
onSelect={(value) => setSelectedAccount(value)}
|
||||
className="w-full mt-1"
|
||||
onChange={(option) => setSelectedAccount(option.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
'use client'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { PageWrapper } from '@/components/foundation'
|
||||
import { FixedProjectCard } from '@/components/projects/project/ProjectCard/FixedProjectCard'
|
||||
import { Button } from '@workspace/ui/components/button'
|
||||
import { Shapes } from 'lucide-react'
|
||||
import { useAuth } from '@clerk/nextjs'
|
||||
|
||||
interface Deployment {
|
||||
id: string
|
||||
name: string
|
||||
repositoryId: string
|
||||
status: 'running' | 'complete' | 'failed'
|
||||
url?: string
|
||||
branch: string
|
||||
createdAt: string
|
||||
createdBy: {
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const [deployments, setDeployments] = useState<Deployment[]>([])
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const { isLoaded: isAuthLoaded, userId } = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDeployments() {
|
||||
if (!isAuthLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
if (!userId) {
|
||||
setError('Not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
// In a real implementation, you would query your GraphQL backend
|
||||
// For now, we'll mock some deployments
|
||||
const mockDeployments: Deployment[] = [
|
||||
{
|
||||
id: 'dep_abc123',
|
||||
name: 'My Project',
|
||||
repositoryId: '123456',
|
||||
status: 'complete',
|
||||
url: 'https://my-project.example.com',
|
||||
branch: 'main',
|
||||
createdAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
||||
createdBy: {
|
||||
name: 'John Doe'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'dep_def456',
|
||||
name: 'Another Project',
|
||||
repositoryId: '789012',
|
||||
status: 'running',
|
||||
branch: 'develop',
|
||||
createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
||||
createdBy: {
|
||||
name: 'Jane Smith'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
setDeployments(mockDeployments);
|
||||
} catch (err) {
|
||||
console.error('Error fetching deployments:', err)
|
||||
setError('Failed to fetch deployments')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchDeployments()
|
||||
}, [isAuthLoaded, userId]);
|
||||
|
||||
return (
|
||||
<PageWrapper
|
||||
header={{
|
||||
title: 'Projects',
|
||||
actions: [{ label: 'Create Project', href: '/projects/create' }]
|
||||
}}
|
||||
layout="bento"
|
||||
className="pb-0"
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="md:col-span-3 flex justify-center items-center min-h-[600px]">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="md:col-span-3 border border-gray-800 rounded-lg min-h-[600px] flex flex-col items-center justify-center p-6">
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-col items-center">
|
||||
<Shapes size={64} className="stroke-current" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold mb-2">Error: {error}</h2>
|
||||
<p className="text-gray-400 text-center max-w-md mb-6">
|
||||
There was an error loading your deployments.
|
||||
</p>
|
||||
<Button
|
||||
className="bg-white text-black hover:bg-gray-200"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Try Again
|
||||
</Button>
|
||||
</div>
|
||||
) : deployments.length === 0 ? (
|
||||
<div className="md:col-span-3 border border-gray-800 rounded-lg min-h-[600px] flex flex-col items-center justify-center p-6">
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-col items-center">
|
||||
<Shapes size={64} className="stroke-current" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold mb-2">Deploy your first app</h2>
|
||||
<p className="text-gray-400 text-center max-w-md mb-6">
|
||||
You haven't deployed any projects yet. Start by importing a repository from your GitHub account.
|
||||
</p>
|
||||
<Button
|
||||
className="bg-white text-black hover:bg-gray-200"
|
||||
onClick={() => window.location.href = '/projects/create'}
|
||||
>
|
||||
Create Project
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="md:col-span-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{deployments.map((deployment) => (
|
||||
<FixedProjectCard
|
||||
project={{
|
||||
id: deployment.id,
|
||||
name: deployment.name,
|
||||
deployments: [{
|
||||
applicationDeploymentRecordData: {
|
||||
url: deployment.url
|
||||
},
|
||||
branch: deployment.branch,
|
||||
createdAt: deployment.createdAt,
|
||||
createdBy: deployment.createdBy
|
||||
}]
|
||||
}}
|
||||
key={deployment.id}
|
||||
status={deployment.status === 'complete' ? 'success' :
|
||||
deployment.status === 'running' ? 'pending' : 'error'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
+20
-12
@@ -42,6 +42,7 @@ export default function ProjectOverviewPage() {
|
||||
const [deployments, setDeployments] = useState<any[]>([]);
|
||||
const [filteredDeployments, setFilteredDeployments] = useState<any[]>([]);
|
||||
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
||||
const [, setSelectedDeploymentId] = useState<string | null>(null);
|
||||
const [deploymentLogs, setDeploymentLogs] = useState<string>('');
|
||||
|
||||
// Load project data
|
||||
@@ -138,18 +139,25 @@ export default function ProjectOverviewPage() {
|
||||
};
|
||||
|
||||
// Handle deployment logs
|
||||
const handleViewLogs = () => {
|
||||
const mockLogs = `[2025-02-12 10:03:12] INFO Starting deployment process for service: ${project?.name}
|
||||
[2025-02-12 10:03:14] INFO Fetching latest commit from main branch (commit: a1b2c3d)
|
||||
[2025-02-12 10:03:15] INFO Building Docker image: registry.company.com/${project?.name}:latest
|
||||
[2025-02-12 10:03:26] INFO Running security scan on built image
|
||||
[2025-02-12 10:03:30] INFO Pushing image to container registry
|
||||
[2025-02-12 10:03:35] INFO Updating deployment configuration
|
||||
[2025-02-12 10:03:40] INFO Scaling down old pods
|
||||
[2025-02-12 10:03:42] INFO Scaling up new pods
|
||||
[2025-02-12 10:03:50] INFO Running health checks on new pods
|
||||
[2025-02-12 10:03:55] INFO Deployment completed successfully
|
||||
[2025-02-12 10:03:56] INFO Service is now live at ${currentDeployment?.applicationDeploymentRecordData?.url}`;
|
||||
const handleViewLogs = (deploymentId: string) => {
|
||||
setSelectedDeploymentId(deploymentId);
|
||||
|
||||
// Mock logs data
|
||||
const mockLogs = `[2025-02-12 10:03:12] INFO Starting deployment process for service: api-gateway
|
||||
[2025-02-12 10:03:14] INFO Fetching latest commit from main branch (commit: a1b2c3d)
|
||||
[2025-02-12 10:03:15] INFO Building Docker image: registry.company.com/api-gateway:latest
|
||||
[2025-02-12 10:03:26] INFO Running security scan on built image
|
||||
[2025-02-12 10:03:27] WARNING Medium severity vulnerability detected in package 'openssl'
|
||||
[2025-02-12 10:03:30] INFO Pushing image to container registry
|
||||
[2025-02-12 10:03:35] INFO Updating Kubernetes deployment
|
||||
[2025-02-12 10:03:40] INFO Scaling down old pods
|
||||
[2025-02-12 10:03:42] INFO Scaling up new pods
|
||||
[2025-02-12 10:03:50] INFO Running health checks on new pods
|
||||
[2025-02-12 10:03:52] ERROR Pod 'api-gateway-7df9bbb500-tx2k4' failed readiness probe (502 Bad Gateway)
|
||||
[2025-02-12 10:03:55] INFO Retrying deployment with previous stable image
|
||||
[2025-02-12 10:04:03] INFO Rolling back to registry.company.com/api-gateway:previous
|
||||
[2025-02-12 10:04:10] INFO Deployment rolled back successfully
|
||||
[2025-02-12 10:04:11] ERROR Deployment failed, please review logs and fix errors`;
|
||||
|
||||
setDeploymentLogs(mockLogs);
|
||||
setIsLogsOpen(true);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Providers } from '@/components/providers'
|
||||
import { ClerkProvider } from '@clerk/nextjs'
|
||||
import '@workspace/ui/globals.css'
|
||||
import { AutoSignInIFrameModal } from '@/components/iframe/auto-sign-in'
|
||||
import { CheckBalanceWrapper } from '@/components/iframe/check-balance-iframe/CheckBalanceWrapper'
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import { CheckBalanceWrapper } from '@/components/iframe/check-balance-iframe/CheckBalanceWrapper'
|
||||
import { AutoSignInIFrameModal } from '@/components/iframe/auto-sign-in'
|
||||
|
||||
// Add root metadata with template pattern
|
||||
export const metadata: Metadata = {
|
||||
@@ -25,14 +25,17 @@ export default function RootLayout({
|
||||
children
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
return (
|
||||
<ClerkProvider signInFallbackRedirectUrl="/home">
|
||||
<ClerkProvider
|
||||
publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
|
||||
signInFallbackRedirectUrl="/home"
|
||||
>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={`${inter.className} `} suppressHydrationWarning>
|
||||
<main>
|
||||
<Providers>{children}</Providers>
|
||||
<div style={{ display: 'none' }}>
|
||||
<AutoSignInIFrameModal />
|
||||
</div>
|
||||
</div>
|
||||
<CheckBalanceWrapper />
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -156,7 +156,7 @@ export default function TestConnectionPage() {
|
||||
|
||||
// Set default org if available
|
||||
if (orgsData.organizations && orgsData.organizations.length > 0) {
|
||||
setSelectedOrg(orgsData.organizations[0].slug)
|
||||
setSelectedOrg(orgsData.organizations[0]!.slug)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching organizations:', error)
|
||||
@@ -230,7 +230,7 @@ const fetchDeployers = async () => {
|
||||
|
||||
// Auto-select first deployer if available
|
||||
if (deployersData.deployers && deployersData.deployers.length > 0) {
|
||||
setSelectedDeployer(deployersData.deployers[0].deployerLrn)
|
||||
setSelectedDeployer(deployersData.deployers[0]!.deployerLrn)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching deployers:', error)
|
||||
|
||||
@@ -194,7 +194,7 @@ Issued At: ${issuedAt}`
|
||||
setDebugInfo(prev => `${prev}\nValidation response: ${JSON.stringify(responseData)}`)
|
||||
|
||||
// If successful, we're done
|
||||
if (response.ok && responseData.success) {
|
||||
if (response.ok && (responseData as any).success) {
|
||||
console.log('Authentication successful!')
|
||||
setDebugInfo(prev => `${prev}\nAuthentication successful!`)
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from '@workspace/ui/components/dropdown-menu'
|
||||
import { cn } from '@workspace/ui/lib/utils'
|
||||
import { MoreVertical } from 'lucide-react'
|
||||
import { MoreVertical, ExternalLink } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import type { ReactNode } from 'react'
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
|
||||
/**
|
||||
* Configuration for header action buttons/links
|
||||
@@ -19,6 +20,7 @@ import type { ReactNode } from 'react'
|
||||
* - Use onClick for JS actions OR href for navigation (not both)
|
||||
* - Multiple visual styles via variant prop
|
||||
* - Optional primary emphasis for main call-to-action
|
||||
* - Optional icon support
|
||||
*/
|
||||
export interface PageAction {
|
||||
/**
|
||||
@@ -27,6 +29,23 @@ export interface PageAction {
|
||||
*/
|
||||
label: string
|
||||
|
||||
/**
|
||||
* Optional icon for the action
|
||||
* @remarks
|
||||
* - Can be string identifier or Lucide icon component
|
||||
* - Common values: 'external-link', 'plus', 'edit', etc.
|
||||
* - Displayed alongside the label
|
||||
*/
|
||||
icon?: string | LucideIcon
|
||||
|
||||
/**
|
||||
* Whether this is an external link
|
||||
* @remarks
|
||||
* - When true, opens in new tab/window
|
||||
* - Automatically adds external link icon if no icon specified
|
||||
*/
|
||||
external?: boolean
|
||||
|
||||
/**
|
||||
* Visual style variant for the button
|
||||
* @remarks
|
||||
@@ -112,6 +131,14 @@ export interface PageHeaderProps {
|
||||
*/
|
||||
subtitle?: string | ReactNode
|
||||
|
||||
/**
|
||||
* Additional description text
|
||||
* @remarks
|
||||
* - Displayed below subtitle
|
||||
* - Useful for longer explanatory text
|
||||
*/
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* Array of action buttons/links
|
||||
* @remarks
|
||||
@@ -119,6 +146,7 @@ export interface PageHeaderProps {
|
||||
* - Mobile: Primary actions shown, secondary in dropdown
|
||||
* - Actions can be buttons (onClick) or links (href)
|
||||
* - Support multiple visual styles via variant prop
|
||||
* - Support icons and external links
|
||||
*
|
||||
* @see {@link PageAction} for detailed action configuration
|
||||
*
|
||||
@@ -128,12 +156,15 @@ export interface PageHeaderProps {
|
||||
* {
|
||||
* label: "Create New",
|
||||
* isPrimary: true,
|
||||
* icon: "plus",
|
||||
* onClick: () => setOpen(true)
|
||||
* },
|
||||
* {
|
||||
* label: "View All",
|
||||
* href: "/items",
|
||||
* variant: "outline"
|
||||
* variant: "outline",
|
||||
* icon: "external-link",
|
||||
* external: true
|
||||
* }
|
||||
* ]}
|
||||
* ```
|
||||
@@ -152,102 +183,11 @@ export interface PageHeaderProps {
|
||||
|
||||
/**
|
||||
* A responsive page header component with title, subtitle, and actions.
|
||||
*
|
||||
* @description
|
||||
* PageHeader provides a consistent header structure with:
|
||||
* - Prominent title as h1
|
||||
* - Optional subtitle or custom component
|
||||
* - Configurable action buttons/links
|
||||
* - Responsive layout with mobile optimization
|
||||
* - Customizable styling
|
||||
*
|
||||
* @keywords header, page-title, action-buttons, responsive-header, foundation-component
|
||||
* @category Layout
|
||||
* @scope Foundation
|
||||
*
|
||||
* @usage
|
||||
* Common patterns:
|
||||
*
|
||||
* Basic title only:
|
||||
* ```tsx
|
||||
* <PageHeader title="Dashboard" />
|
||||
* ```
|
||||
*
|
||||
* With subtitle and primary action:
|
||||
* ```tsx
|
||||
* <PageHeader
|
||||
* title="Projects"
|
||||
* subtitle="Your active projects"
|
||||
* actions={[
|
||||
* { label: "New Project", isPrimary: true, onClick: handleCreate }
|
||||
* ]}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* With search component and multiple actions:
|
||||
* ```tsx
|
||||
* <PageHeader
|
||||
* title="Team Members"
|
||||
* subtitle={<SearchInput placeholder="Search members..." />}
|
||||
* actions={[
|
||||
* { label: "Invite", isPrimary: true, onClick: handleInvite },
|
||||
* { label: "Export", variant: "outline", onClick: handleExport },
|
||||
* { label: "Settings", href: "/team/settings", variant: "ghost" }
|
||||
* ]}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* With navigation actions:
|
||||
* ```tsx
|
||||
* <PageHeader
|
||||
* title="Edit Profile"
|
||||
* actions={[
|
||||
* { label: "Save", isPrimary: true, onClick: handleSave },
|
||||
* { label: "Cancel", href: "/profile", variant: "ghost" }
|
||||
* ]}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <PageHeader
|
||||
* title="Dashboard"
|
||||
* subtitle="Welcome back!"
|
||||
* actions={[
|
||||
* {
|
||||
* label: "New Item",
|
||||
* isPrimary: true,
|
||||
* onClick: () => console.log("clicked")
|
||||
* }
|
||||
* ]}
|
||||
* className="mb-8"
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @related {@link PageWrapper} - Often used together for page layout
|
||||
* @related {@link Button} - Used for rendering actions
|
||||
* @composition Uses {@link DropdownMenu} for mobile action menu
|
||||
*
|
||||
* @cssUtilities
|
||||
* - flex-col/flex-row: Responsive layout
|
||||
* - gap-6/gap-2: Consistent spacing
|
||||
* - text-2xl/text-[30px]: Responsive typography
|
||||
* - text-foreground/text-muted-foreground: Text hierarchy
|
||||
*
|
||||
* @accessibility
|
||||
* - Uses semantic h1 for title
|
||||
* - Maintains text contrast ratios
|
||||
* - Dropdown menu is keyboard navigable
|
||||
* - Preserves action button/link semantics
|
||||
*
|
||||
* @performance
|
||||
* - Conditional rendering of subtitle and actions
|
||||
* - Mobile-first CSS with responsive modifiers
|
||||
* - Efficient action rendering with key prop
|
||||
*/
|
||||
export default function PageHeader({
|
||||
title,
|
||||
subtitle,
|
||||
description,
|
||||
actions = [],
|
||||
className
|
||||
}: PageHeaderProps) {
|
||||
@@ -255,21 +195,51 @@ export default function PageHeader({
|
||||
const primaryActions = actions.filter((action) => action.isPrimary)
|
||||
const secondaryActions = actions.filter((action) => !action.isPrimary)
|
||||
|
||||
// Get icon component from string or return the component directly
|
||||
const getIconComponent = (icon: string | LucideIcon | undefined) => {
|
||||
if (!icon) return null
|
||||
|
||||
if (typeof icon === 'string') {
|
||||
switch (icon) {
|
||||
case 'external-link':
|
||||
return ExternalLink
|
||||
default:
|
||||
return ExternalLink // fallback
|
||||
}
|
||||
}
|
||||
|
||||
return icon
|
||||
}
|
||||
|
||||
// Render an action (either as button or link)
|
||||
const renderAction = (action: PageAction, key: string) => {
|
||||
const variant = action.variant || (action.isPrimary ? 'default' : 'outline')
|
||||
const IconComponent = getIconComponent(action.icon || (action.external ? 'external-link' : undefined))
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{action.label}
|
||||
{IconComponent && <IconComponent className="h-4 w-4 ml-2" />}
|
||||
</>
|
||||
)
|
||||
|
||||
if (action.href) {
|
||||
return (
|
||||
<Button key={key} variant={variant} asChild>
|
||||
<Link href={action.href}>{action.label}</Link>
|
||||
<Link
|
||||
href={action.href}
|
||||
target={action.external ? '_blank' : undefined}
|
||||
rel={action.external ? 'noopener noreferrer' : undefined}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button key={key} variant={variant} onClick={action.onClick}>
|
||||
{action.label}
|
||||
{content}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -292,6 +262,11 @@ export default function PageHeader({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{description && (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{actions.length > 0 && (
|
||||
@@ -320,7 +295,16 @@ export default function PageHeader({
|
||||
{secondaryActions.map((action) =>
|
||||
action.href ? (
|
||||
<DropdownMenuItem asChild key={action.label}>
|
||||
<Link href={action.href}>{action.label}</Link>
|
||||
<Link
|
||||
href={action.href}
|
||||
target={action.external ? '_blank' : undefined}
|
||||
rel={action.external ? 'noopener noreferrer' : undefined}
|
||||
>
|
||||
{action.label}
|
||||
{getIconComponent(action.icon || (action.external ? 'external-link' : undefined)) && (
|
||||
<ExternalLink className="h-4 w-4 ml-2" />
|
||||
)}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
@@ -340,4 +324,4 @@ export default function PageHeader({
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import { useWallet } from '@/context/WalletContextProvider'
|
||||
import { useWallet } from '@/context/WalletContext'
|
||||
import { Button } from '@workspace/ui/components/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { ConfigureStep } from '@/components/onboarding/configure-step'
|
||||
import { ConnectStep } from '@/components/onboarding/connect-step'
|
||||
import { DeployStep } from '@/components/onboarding/deploy-step'
|
||||
import { useOnboarding } from '@/components/onboarding/store'
|
||||
import { useOnboarding } from '@/components/onboarding/useOnboarding'
|
||||
import { ScrollArea } from '@workspace/ui/components/scroll-area'
|
||||
import { SidebarNav } from './sidebar'
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ const OnboardingDialog: React.FC<OnboardingDialogProps> = ({
|
||||
defaultOpen = false,
|
||||
onClose
|
||||
}) => {
|
||||
const { nextStep, setFormData, formData, currentStep } = useOnboarding()
|
||||
const { nextStep, setFormData, formData, currentStep, previousStep } = useOnboarding()
|
||||
const [selectedRepo, setSelectedRepo] = useState<string>(formData.githubRepo || '')
|
||||
const [isImportMode, setIsImportMode] = useState(true)
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen)
|
||||
@@ -489,7 +489,7 @@ const OnboardingDialog: React.FC<OnboardingDialogProps> = ({
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-zinc-400 bg-zinc-900 border-zinc-800 hover:bg-zinc-800"
|
||||
onClick={() => useOnboarding.getState().previousStep()}
|
||||
onClick={() => previousStep()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
@@ -527,7 +527,7 @@ const OnboardingDialog: React.FC<OnboardingDialogProps> = ({
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-zinc-400 bg-zinc-900 border-zinc-800 hover:bg-zinc-800"
|
||||
onClick={() => useOnboarding.getState().previousStep()}
|
||||
onClick={() => previousStep()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
// src/components/onboarding/configure-step/configure-step.tsx
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { PlusCircle, Loader2, AlertTriangle, Info } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useOnboarding } from '@/components/onboarding/useOnboarding'
|
||||
import { useGQLClient } from '@/context'
|
||||
import { useWallet } from '@/context/WalletContext'
|
||||
import { Button } from '@workspace/ui/components/button'
|
||||
import { Input } from '@workspace/ui/components/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@workspace/ui/components/select'
|
||||
import { Checkbox } from '@workspace/ui/components/checkbox'
|
||||
import { Label } from '@workspace/ui/components/label'
|
||||
import { Alert, AlertDescription } from '@workspace/ui/components/alert'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card'
|
||||
import { Badge } from '@workspace/ui/components/badge'
|
||||
import { Button } from '@workspace/ui/components/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '@workspace/ui/components/card'
|
||||
import { Checkbox } from '@workspace/ui/components/checkbox'
|
||||
import { Input } from '@workspace/ui/components/input'
|
||||
import { Label } from '@workspace/ui/components/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@workspace/ui/components/select'
|
||||
import { AlertTriangle, Info, Loader2, PlusCircle } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { adaptDeployers } from '../../../utils/typeAdapters'
|
||||
|
||||
interface Deployer {
|
||||
deployerLrn: string
|
||||
@@ -34,42 +46,40 @@ export function ConfigureStep() {
|
||||
const { nextStep, previousStep, setFormData, formData } = useOnboarding()
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
|
||||
// Backend data
|
||||
const [deployers, setDeployers] = useState<Deployer[]>([])
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([])
|
||||
const [isLoadingDeployers, setIsLoadingDeployers] = useState(true)
|
||||
const [isLoadingOrgs, setIsLoadingOrgs] = useState(true)
|
||||
|
||||
|
||||
// Form state
|
||||
const [deployOption, setDeployOption] = useState<'auction' | 'lrn'>(
|
||||
formData.deploymentType as ('auction' | 'lrn') || 'lrn' // Default to LRN for simplicity
|
||||
(formData.deploymentType as 'auction' | 'lrn') || 'lrn' // Default to LRN for simplicity
|
||||
)
|
||||
const [numberOfDeployers, setNumberOfDeployers] = useState<string>(
|
||||
formData.deployerCount || "1"
|
||||
)
|
||||
const [maxPrice, setMaxPrice] = useState<string>(
|
||||
formData.maxPrice || "1000"
|
||||
formData.deployerCount || '1'
|
||||
)
|
||||
const [maxPrice, setMaxPrice] = useState<string>(formData.maxPrice || '1000')
|
||||
const [selectedLrn, setSelectedLrn] = useState<string>(
|
||||
formData.selectedLrn || ""
|
||||
formData.selectedLrn || ''
|
||||
)
|
||||
const [selectedOrg, setSelectedOrg] = useState<string>(
|
||||
formData.selectedOrg || ""
|
||||
formData.selectedOrg || ''
|
||||
)
|
||||
const [envVars, setEnvVars] = useState<{ key: string; value: string; environments: string[] }[]>([
|
||||
{ key: '', value: '', environments: ['Production'] }
|
||||
])
|
||||
|
||||
const [envVars, setEnvVars] = useState<
|
||||
{ key: string; value: string; environments: string[] }[]
|
||||
>([{ key: '', value: '', environments: ['Production'] }])
|
||||
|
||||
// Contexts
|
||||
const gqlClient = useGQLClient()
|
||||
const { wallet } = useWallet()
|
||||
|
||||
|
||||
// Handle hydration mismatch by waiting for mount
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
|
||||
// Fetch deployers and organizations on mount
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
@@ -77,27 +87,36 @@ export function ConfigureStep() {
|
||||
fetchOrganizations()
|
||||
}
|
||||
}, [mounted])
|
||||
|
||||
|
||||
// Initialize environment variables from formData if available
|
||||
useEffect(() => {
|
||||
if (formData.environmentVariables && Array.isArray(formData.environmentVariables)) {
|
||||
setEnvVars(formData.environmentVariables.length > 0 ? formData.environmentVariables : [
|
||||
{ key: '', value: '', environments: ['Production'] }
|
||||
])
|
||||
if (
|
||||
formData.environmentVariables &&
|
||||
Array.isArray(formData.environmentVariables)
|
||||
) {
|
||||
setEnvVars(
|
||||
formData.environmentVariables.length > 0
|
||||
? formData.environmentVariables
|
||||
: [{ key: '', value: '', environments: ['Production'] }]
|
||||
)
|
||||
}
|
||||
}, [formData.environmentVariables])
|
||||
|
||||
|
||||
// Fetch deployers from backend
|
||||
const fetchDeployers = async () => {
|
||||
try {
|
||||
setIsLoadingDeployers(true)
|
||||
const deployersData = await gqlClient.getDeployers()
|
||||
console.log('Available deployers:', deployersData)
|
||||
setDeployers(deployersData.deployers || [])
|
||||
|
||||
setDeployers(adaptDeployers(deployersData.deployers || []))
|
||||
|
||||
// Auto-select first deployer if available and none selected
|
||||
if (deployersData.deployers && deployersData.deployers.length > 0 && !selectedLrn) {
|
||||
setSelectedLrn(deployersData.deployers[0].deployerLrn)
|
||||
if (
|
||||
deployersData.deployers &&
|
||||
deployersData.deployers.length > 0 &&
|
||||
!selectedLrn
|
||||
) {
|
||||
setSelectedLrn(deployersData.deployers[0]!.deployerLrn)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching deployers:', error)
|
||||
@@ -106,7 +125,7 @@ export function ConfigureStep() {
|
||||
setIsLoadingDeployers(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fetch organizations from backend
|
||||
const fetchOrganizations = async () => {
|
||||
try {
|
||||
@@ -114,10 +133,14 @@ export function ConfigureStep() {
|
||||
const orgsData = await gqlClient.getOrganizations()
|
||||
console.log('Available organizations:', orgsData)
|
||||
setOrganizations(orgsData.organizations || [])
|
||||
|
||||
|
||||
// Auto-select first organization if available and none selected
|
||||
if (orgsData.organizations && orgsData.organizations.length > 0 && !selectedOrg) {
|
||||
setSelectedOrg(orgsData.organizations[0].slug)
|
||||
if (
|
||||
orgsData.organizations &&
|
||||
orgsData.organizations.length > 0 &&
|
||||
!selectedOrg
|
||||
) {
|
||||
setSelectedOrg(orgsData.organizations[0]!.slug)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching organizations:', error)
|
||||
@@ -126,53 +149,66 @@ export function ConfigureStep() {
|
||||
setIsLoadingOrgs(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add an empty environment variable row
|
||||
const addEnvVar = () => {
|
||||
setEnvVars([...envVars, { key: '', value: '', environments: ['Production'] }])
|
||||
setEnvVars([
|
||||
...envVars,
|
||||
{ key: '', value: '', environments: ['Production'] }
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
// Remove environment variable row
|
||||
const removeEnvVar = (index: number) => {
|
||||
if (envVars.length > 1) {
|
||||
setEnvVars(envVars.filter((_, i) => i !== index))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update environment variable
|
||||
const updateEnvVar = (index: number, field: 'key' | 'value', value: string) => {
|
||||
const updateEnvVar = (
|
||||
index: number,
|
||||
field: 'key' | 'value',
|
||||
value: string
|
||||
) => {
|
||||
const newEnvVars = [...envVars]
|
||||
newEnvVars[index][field] = value
|
||||
if (newEnvVars[index]) {
|
||||
newEnvVars[index][field] = value
|
||||
}
|
||||
setEnvVars(newEnvVars)
|
||||
}
|
||||
|
||||
|
||||
// Toggle environment for variable
|
||||
const toggleEnvironment = (index: number, environment: string) => {
|
||||
const newEnvVars = [...envVars]
|
||||
const currentEnvs = newEnvVars[index].environments
|
||||
|
||||
if (currentEnvs.includes(environment)) {
|
||||
newEnvVars[index].environments = currentEnvs.filter(env => env !== environment)
|
||||
} else {
|
||||
newEnvVars[index].environments = [...currentEnvs, environment]
|
||||
if (newEnvVars[index]?.environments) {
|
||||
const currentEnvs = newEnvVars[index].environments
|
||||
|
||||
if (currentEnvs.includes(environment)) {
|
||||
newEnvVars[index].environments = currentEnvs.filter(
|
||||
(env) => env !== environment
|
||||
)
|
||||
} else {
|
||||
newEnvVars[index].environments = [...currentEnvs, environment]
|
||||
}
|
||||
|
||||
// Ensure at least one environment is selected
|
||||
if (newEnvVars[index].environments.length === 0) {
|
||||
newEnvVars[index].environments = ['Production']
|
||||
}
|
||||
|
||||
setEnvVars(newEnvVars)
|
||||
}
|
||||
|
||||
// Ensure at least one environment is selected
|
||||
if (newEnvVars[index].environments.length === 0) {
|
||||
newEnvVars[index].environments = ['Production']
|
||||
}
|
||||
|
||||
setEnvVars(newEnvVars)
|
||||
}
|
||||
|
||||
|
||||
// Toggle deployment option
|
||||
const toggleDeployOption = (option: 'auction' | 'lrn') => {
|
||||
setDeployOption(option)
|
||||
}
|
||||
|
||||
|
||||
// Get selected deployer details
|
||||
const selectedDeployer = deployers.find(d => d.deployerLrn === selectedLrn)
|
||||
|
||||
const selectedDeployer = deployers.find((d) => d.deployerLrn === selectedLrn)
|
||||
|
||||
// Validate form
|
||||
const canProceed = () => {
|
||||
if (deployOption === 'lrn' && !selectedLrn) return false
|
||||
@@ -180,17 +216,19 @@ export function ConfigureStep() {
|
||||
if (!wallet?.address) return false
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// Handle next step
|
||||
const handleNext = () => {
|
||||
if (!canProceed()) {
|
||||
toast.error('Please complete all required fields')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Filter out empty environment variables
|
||||
const validEnvVars = envVars.filter(env => env.key.trim() && env.value.trim())
|
||||
|
||||
const validEnvVars = envVars.filter(
|
||||
(env) => env.key.trim() && env.value.trim()
|
||||
)
|
||||
|
||||
// Save configuration to form data
|
||||
setFormData({
|
||||
deploymentType: deployOption,
|
||||
@@ -201,33 +239,58 @@ export function ConfigureStep() {
|
||||
paymentAddress: wallet?.address,
|
||||
environmentVariables: validEnvVars
|
||||
})
|
||||
|
||||
|
||||
nextStep()
|
||||
}
|
||||
|
||||
|
||||
// Don't render UI until after mount to prevent hydration mismatch
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
// Determine if dark mode is active
|
||||
const isDarkMode = resolvedTheme === 'dark'
|
||||
|
||||
|
||||
// Get deployment mode info
|
||||
const isTemplateMode = formData.deploymentMode === 'template'
|
||||
const selectedItem = isTemplateMode ? formData.template?.name : formData.githubRepo
|
||||
|
||||
const selectedItem = isTemplateMode
|
||||
? formData.template?.name
|
||||
: formData.githubRepo
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col p-8 overflow-y-auto">
|
||||
{/* Configure icon and header */}
|
||||
<div className="flex flex-col items-center justify-center mb-8">
|
||||
<div className="mb-4">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={isDarkMode ? "text-white" : "text-black"}>
|
||||
<path d="M12 20h9" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={isDarkMode ? 'text-white' : 'text-black'}
|
||||
>
|
||||
<path
|
||||
d="M12 20h9"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className={`text-2xl font-medium text-center mb-2 ${isDarkMode ? "text-white" : "text-zinc-900"}`}>Configure</h2>
|
||||
<h2
|
||||
className={`text-2xl font-medium text-center mb-2 ${isDarkMode ? 'text-white' : 'text-zinc-900'}`}
|
||||
>
|
||||
Configure
|
||||
</h2>
|
||||
<p className={`text-center text-zinc-500 max-w-md`}>
|
||||
Define the deployment type
|
||||
</p>
|
||||
@@ -246,7 +309,9 @@ export function ConfigureStep() {
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Type:</span>
|
||||
<Badge variant="secondary">{isTemplateMode ? 'Template' : 'Repository'}</Badge>
|
||||
<Badge variant="secondary">
|
||||
{isTemplateMode ? 'Template' : 'Repository'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Source:</span>
|
||||
@@ -262,24 +327,33 @@ export function ConfigureStep() {
|
||||
|
||||
{/* Organization Selection */}
|
||||
<div className="mb-6">
|
||||
<Label htmlFor="organization" className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}>
|
||||
<Label
|
||||
htmlFor="organization"
|
||||
className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}
|
||||
>
|
||||
Organization *
|
||||
</Label>
|
||||
{isLoadingOrgs ? (
|
||||
<div className="flex items-center justify-center p-3 border rounded-md">
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||
<span className="text-sm text-muted-foreground">Loading organizations...</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Loading organizations...
|
||||
</span>
|
||||
</div>
|
||||
) : organizations.length === 0 ? (
|
||||
<Alert>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
No organizations found. You need to be part of at least one organization.
|
||||
No organizations found. You need to be part of at least one
|
||||
organization.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<Select value={selectedOrg} onValueChange={setSelectedOrg}>
|
||||
<SelectTrigger id="organization" className={isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}>
|
||||
<SelectTrigger
|
||||
id="organization"
|
||||
className={isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}
|
||||
>
|
||||
<SelectValue placeholder="Select organization" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -295,41 +369,60 @@ export function ConfigureStep() {
|
||||
|
||||
{/* Deployment options */}
|
||||
<div className="mb-6">
|
||||
<Label className={`text-sm mb-3 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}>
|
||||
<Label
|
||||
className={`text-sm mb-3 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}
|
||||
>
|
||||
Deployment Type
|
||||
</Label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
variant={deployOption === 'lrn' ? "default" : "outline"}
|
||||
className={`py-3 ${deployOption === 'lrn'
|
||||
? (isDarkMode ? 'bg-zinc-800 text-white' : 'bg-zinc-800 text-white')
|
||||
: (isDarkMode ? 'bg-transparent border-zinc-700 text-zinc-400' : 'bg-transparent border-zinc-300 text-zinc-600')}`}
|
||||
variant={deployOption === 'lrn' ? 'default' : 'outline'}
|
||||
className={`py-3 ${
|
||||
deployOption === 'lrn'
|
||||
? isDarkMode
|
||||
? 'bg-zinc-800 text-white'
|
||||
: 'bg-zinc-800 text-white'
|
||||
: isDarkMode
|
||||
? 'bg-transparent border-zinc-700 text-zinc-400'
|
||||
: 'bg-transparent border-zinc-300 text-zinc-600'
|
||||
}`}
|
||||
onClick={() => toggleDeployOption('lrn')}
|
||||
>
|
||||
Deployer LRN
|
||||
</Button>
|
||||
<Button
|
||||
variant={deployOption === 'auction' ? "default" : "outline"}
|
||||
className={`py-3 ${deployOption === 'auction'
|
||||
? (isDarkMode ? 'bg-zinc-800 text-white' : 'bg-zinc-800 text-white')
|
||||
: (isDarkMode ? 'bg-transparent border-zinc-700 text-zinc-400' : 'bg-transparent border-zinc-300 text-zinc-600')}`}
|
||||
variant={deployOption === 'auction' ? 'default' : 'outline'}
|
||||
className={`py-3 ${
|
||||
deployOption === 'auction'
|
||||
? isDarkMode
|
||||
? 'bg-zinc-800 text-white'
|
||||
: 'bg-zinc-800 text-white'
|
||||
: isDarkMode
|
||||
? 'bg-transparent border-zinc-700 text-zinc-400'
|
||||
: 'bg-transparent border-zinc-300 text-zinc-600'
|
||||
}`}
|
||||
onClick={() => toggleDeployOption('auction')}
|
||||
>
|
||||
Create Auction
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{deployOption === 'lrn' ? (
|
||||
/* LRN Deployment Settings */
|
||||
<div className="mb-6">
|
||||
<Label htmlFor="lrn" className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}>
|
||||
<Label
|
||||
htmlFor="lrn"
|
||||
className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}
|
||||
>
|
||||
Select Deployer LRN *
|
||||
</Label>
|
||||
{isLoadingDeployers ? (
|
||||
<div className="flex items-center justify-center p-3 border rounded-md">
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||
<span className="text-sm text-muted-foreground">Loading deployers...</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Loading deployers...
|
||||
</span>
|
||||
</div>
|
||||
) : deployers.length === 0 ? (
|
||||
<Alert>
|
||||
@@ -341,12 +434,20 @@ export function ConfigureStep() {
|
||||
) : (
|
||||
<>
|
||||
<Select value={selectedLrn} onValueChange={setSelectedLrn}>
|
||||
<SelectTrigger id="lrn" className={isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}>
|
||||
<SelectTrigger
|
||||
id="lrn"
|
||||
className={
|
||||
isDarkMode ? 'border-zinc-700' : 'border-zinc-300'
|
||||
}
|
||||
>
|
||||
<SelectValue placeholder="Select a deployer" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{deployers.map((deployer) => (
|
||||
<SelectItem key={deployer.deployerLrn} value={deployer.deployerLrn}>
|
||||
<SelectItem
|
||||
key={deployer.deployerLrn}
|
||||
value={deployer.deployerLrn}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span>{deployer.deployerLrn}</span>
|
||||
{deployer.minimumPayment && (
|
||||
@@ -359,15 +460,24 @@ export function ConfigureStep() {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
|
||||
{/* Deployer Details */}
|
||||
{selectedDeployer && (
|
||||
<div className="mt-3 p-3 bg-muted rounded-md">
|
||||
<div className="text-sm space-y-1">
|
||||
<div><strong>API URL:</strong> {selectedDeployer.deployerApiUrl}</div>
|
||||
<div><strong>Base Domain:</strong> {selectedDeployer.baseDomain}</div>
|
||||
<div>
|
||||
<strong>API URL:</strong>{' '}
|
||||
{selectedDeployer.deployerApiUrl}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Base Domain:</strong>{' '}
|
||||
{selectedDeployer.baseDomain}
|
||||
</div>
|
||||
{selectedDeployer.minimumPayment && (
|
||||
<div><strong>Minimum Payment:</strong> {selectedDeployer.minimumPayment}</div>
|
||||
<div>
|
||||
<strong>Minimum Payment:</strong>{' '}
|
||||
{selectedDeployer.minimumPayment}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -379,11 +489,20 @@ export function ConfigureStep() {
|
||||
/* Auction Settings */
|
||||
<div className="grid grid-cols-2 gap-4 mb-6">
|
||||
<div>
|
||||
<Label htmlFor="deployers" className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}>
|
||||
<Label
|
||||
htmlFor="deployers"
|
||||
className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}
|
||||
>
|
||||
Number of Deployers
|
||||
</Label>
|
||||
<Select value={numberOfDeployers} onValueChange={setNumberOfDeployers}>
|
||||
<SelectTrigger id="deployers" className={isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}>
|
||||
<Select
|
||||
value={numberOfDeployers}
|
||||
onValueChange={setNumberOfDeployers}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="deployers"
|
||||
className={isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}
|
||||
>
|
||||
<SelectValue placeholder="Select number" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -396,11 +515,17 @@ export function ConfigureStep() {
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="maxPrice" className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}>
|
||||
<Label
|
||||
htmlFor="maxPrice"
|
||||
className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}
|
||||
>
|
||||
Maximum Price (aint)
|
||||
</Label>
|
||||
<Select value={maxPrice} onValueChange={setMaxPrice}>
|
||||
<SelectTrigger id="maxPrice" className={isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}>
|
||||
<SelectTrigger
|
||||
id="maxPrice"
|
||||
className={isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}
|
||||
>
|
||||
<SelectValue placeholder="Select price" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -413,43 +538,58 @@ export function ConfigureStep() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Payment Address */}
|
||||
<div className="mb-6">
|
||||
<Label className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}>
|
||||
<Label
|
||||
className={`text-sm mb-2 block ${isDarkMode ? 'text-zinc-400' : 'text-zinc-700'}`}
|
||||
>
|
||||
Payment Address
|
||||
</Label>
|
||||
<div className={`p-3 border rounded-md bg-muted ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}>
|
||||
<div
|
||||
className={`p-3 border rounded-md bg-muted ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}
|
||||
>
|
||||
<div className="text-sm font-mono break-all">
|
||||
{wallet?.address || 'No wallet connected'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Environment Variables */}
|
||||
<div className="mb-6">
|
||||
<Label className={`text-sm font-medium mb-2 block ${isDarkMode ? 'text-white' : 'text-zinc-900'}`}>
|
||||
<Label
|
||||
className={`text-sm font-medium mb-2 block ${isDarkMode ? 'text-white' : 'text-zinc-900'}`}
|
||||
>
|
||||
Environment Variables
|
||||
</Label>
|
||||
<div className={`border rounded-md p-4 ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}>
|
||||
<div
|
||||
className={`border rounded-md p-4 ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}
|
||||
>
|
||||
{envVars.map((envVar, index) => (
|
||||
<div key={index} className="space-y-2 mb-4 pb-4 border-b border-muted last:border-b-0 last:mb-0 last:pb-0">
|
||||
<div
|
||||
key={index}
|
||||
className="space-y-2 mb-4 pb-4 border-b border-muted last:border-b-0 last:mb-0 last:pb-0"
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Input
|
||||
<Input
|
||||
placeholder="KEY"
|
||||
value={envVar.key}
|
||||
onChange={(e) => updateEnvVar(index, 'key', e.target.value)}
|
||||
className={`bg-transparent ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}
|
||||
/>
|
||||
<Input
|
||||
<Input
|
||||
placeholder="VALUE"
|
||||
value={envVar.value}
|
||||
onChange={(e) => updateEnvVar(index, 'value', e.target.value)}
|
||||
onChange={(e) =>
|
||||
updateEnvVar(index, 'value', e.target.value)
|
||||
}
|
||||
className={`bg-transparent ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-xs text-muted-foreground">Environments:</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Environments:
|
||||
</span>
|
||||
{['Production', 'Preview', 'Development'].map((env) => (
|
||||
<div key={env} className="flex items-center gap-1">
|
||||
<Checkbox
|
||||
@@ -475,8 +615,8 @@ export function ConfigureStep() {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
<Button
|
||||
variant="outline"
|
||||
className={`w-full mt-2 ${isDarkMode ? 'text-zinc-400 border-zinc-700' : 'text-zinc-600 border-zinc-300'}`}
|
||||
onClick={addEnvVar}
|
||||
>
|
||||
@@ -485,17 +625,17 @@ export function ConfigureStep() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Navigation buttons */}
|
||||
<div className="flex justify-between items-center mt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
<Button
|
||||
variant="outline"
|
||||
className={`bg-transparent ${isDarkMode ? 'text-zinc-400 border-zinc-700' : 'text-zinc-600 border-zinc-300'}`}
|
||||
onClick={previousStep}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
variant="default"
|
||||
className={`${isDarkMode ? 'bg-zinc-800' : 'bg-zinc-800'} text-white hover:bg-zinc-700`}
|
||||
onClick={handleNext}
|
||||
@@ -507,4 +647,4 @@ export function ConfigureStep() {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
// src/components/onboarding/connect-step/connect-step.tsx
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Github, Wallet, CheckCircle2, AlertTriangle, Loader2, ExternalLink, ChevronDown } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { SignIn } from '@clerk/nextjs'
|
||||
import { GitHubBackendAuth } from '@/components/GitHubBackendAuth'
|
||||
import { useOnboarding } from '@/components/onboarding/useOnboarding'
|
||||
import { AVAILABLE_TEMPLATES, type TemplateDetail } from '@/constants/templates'
|
||||
import { useAuthStatus } from '@/hooks/useAuthStatus'
|
||||
import { useRepoData } from '@/hooks/useRepoData'
|
||||
import type { Template } from '@/types/onboarding'
|
||||
import { adaptOptionalTemplate } from '@/utils/typeAdapters'
|
||||
import { SignIn } from '@clerk/nextjs'
|
||||
import { Alert, AlertDescription } from '@workspace/ui/components/alert'
|
||||
import { Button } from '@workspace/ui/components/button'
|
||||
import { Card, CardContent } from '@workspace/ui/components/card'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger
|
||||
} from '@workspace/ui/components/collapsible'
|
||||
import { Input } from '@workspace/ui/components/input'
|
||||
import { Label } from '@workspace/ui/components/label'
|
||||
import { Alert, AlertDescription } from '@workspace/ui/components/alert'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@workspace/ui/components/collapsible'
|
||||
import {
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
ExternalLink,
|
||||
Github,
|
||||
Loader2,
|
||||
Wallet
|
||||
} from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { GitHubBackendAuth } from '@/components/GitHubBackendAuth'
|
||||
import { AVAILABLE_TEMPLATES, type TemplateDetail } from '@/constants/templates'
|
||||
|
||||
interface Repository {
|
||||
id: string | number
|
||||
@@ -29,23 +43,25 @@ export function ConnectStep() {
|
||||
const { nextStep, setFormData, formData } = useOnboarding()
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
|
||||
// Repository vs Template selection
|
||||
const [selectedRepo, setSelectedRepo] = useState<string>(formData.githubRepo || '')
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<TemplateDetail | undefined>(
|
||||
formData.template || undefined
|
||||
const [selectedRepo, setSelectedRepo] = useState<string>(
|
||||
formData.githubRepo || ''
|
||||
)
|
||||
const [selectedTemplate, setSelectedTemplate] = useState(
|
||||
adaptOptionalTemplate(formData.template)
|
||||
)
|
||||
const [projectName, setProjectName] = useState<string>(
|
||||
formData.projectName || ''
|
||||
)
|
||||
const [projectName, setProjectName] = useState<string>(formData.projectName || '')
|
||||
const [isImportMode, setIsImportMode] = useState(true)
|
||||
|
||||
|
||||
// Auth status and warning display
|
||||
const [showAuthWarning, setShowAuthWarning] = useState(false)
|
||||
|
||||
|
||||
// Auth status hook
|
||||
const {
|
||||
clerk,
|
||||
wallet,
|
||||
backend,
|
||||
isFullyAuthenticated,
|
||||
isReady,
|
||||
missing,
|
||||
@@ -53,34 +69,34 @@ export function ConnectStep() {
|
||||
connectWallet,
|
||||
checkGithubBackendAuth
|
||||
} = useAuthStatus()
|
||||
|
||||
|
||||
// Repository data
|
||||
const { repoData: repositories, isLoading: isLoadingRepos } = useRepoData('')
|
||||
|
||||
|
||||
// Handle hydration mismatch by waiting for mount
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
|
||||
// Auto-hide auth warning when fully authenticated
|
||||
useEffect(() => {
|
||||
if (isFullyAuthenticated) {
|
||||
setShowAuthWarning(false)
|
||||
}
|
||||
}, [isFullyAuthenticated])
|
||||
|
||||
|
||||
// Handle repository selection
|
||||
const handleRepoSelect = (repo: string) => {
|
||||
setSelectedRepo(repo)
|
||||
setSelectedTemplate(undefined)
|
||||
setFormData({
|
||||
setFormData({
|
||||
githubRepo: repo,
|
||||
template: undefined,
|
||||
deploymentMode: 'repository',
|
||||
projectName
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Handle template selection
|
||||
const handleTemplateSelect = (template: TemplateDetail) => {
|
||||
setSelectedTemplate(template)
|
||||
@@ -90,41 +106,43 @@ export function ConnectStep() {
|
||||
const suggestedName = `my-${template.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`
|
||||
setProjectName(suggestedName)
|
||||
}
|
||||
setFormData({
|
||||
setFormData({
|
||||
template: template,
|
||||
githubRepo: '',
|
||||
deploymentMode: 'template',
|
||||
projectName: projectName || `my-${template.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`
|
||||
projectName:
|
||||
projectName ||
|
||||
`my-${template.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Handle mode toggle between import and template
|
||||
const toggleMode = (mode: 'import' | 'template') => {
|
||||
setIsImportMode(mode === 'import')
|
||||
// Clear selections when switching modes
|
||||
if (mode === 'import') {
|
||||
setSelectedTemplate(undefined)
|
||||
setFormData({
|
||||
setFormData({
|
||||
template: undefined,
|
||||
deploymentMode: 'repository',
|
||||
projectName
|
||||
})
|
||||
} else {
|
||||
setSelectedRepo('')
|
||||
setFormData({
|
||||
setFormData({
|
||||
githubRepo: '',
|
||||
deploymentMode: 'template',
|
||||
projectName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle project name change
|
||||
const handleProjectNameChange = (value: string) => {
|
||||
setProjectName(value)
|
||||
setFormData({ projectName: value })
|
||||
}
|
||||
|
||||
|
||||
// Handle wallet connection
|
||||
const handleConnectWallet = async () => {
|
||||
try {
|
||||
@@ -135,7 +153,7 @@ export function ConnectStep() {
|
||||
toast.error('Failed to connect wallet')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle GitHub backend auth status change
|
||||
const handleGithubAuthChange = async (isAuthenticated: boolean) => {
|
||||
await checkGithubBackendAuth()
|
||||
@@ -143,7 +161,7 @@ export function ConnectStep() {
|
||||
toast.success('GitHub backend authentication completed!')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle next step
|
||||
const handleNext = () => {
|
||||
if (!isFullyAuthenticated) {
|
||||
@@ -163,60 +181,68 @@ export function ConnectStep() {
|
||||
}
|
||||
|
||||
// For repository import, project name is optional but we'll use repo name as fallback
|
||||
const finalProjectName = projectName.trim() || (isImportMode ? selectedRepo.split('/')[1] : '')
|
||||
const finalProjectName =
|
||||
projectName.trim() || (isImportMode ? selectedRepo.split('/')[1] : '')
|
||||
|
||||
// Set final form data and proceed
|
||||
setFormData({
|
||||
deploymentMode: isImportMode ? 'repository' : 'template',
|
||||
githubRepo: isImportMode ? selectedRepo : '',
|
||||
template: !isImportMode ? selectedTemplate : undefined,
|
||||
template: !isImportMode ? (selectedTemplate as Template) : undefined,
|
||||
projectName: finalProjectName
|
||||
})
|
||||
|
||||
nextStep()
|
||||
}
|
||||
|
||||
|
||||
// Don't render UI until after mount to prevent hydration mismatch
|
||||
if (!mounted || !isReady) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4" />
|
||||
<p className="text-sm text-zinc-500">Loading authentication status...</p>
|
||||
<p className="text-sm text-zinc-500">
|
||||
Loading authentication status...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Determine if dark mode is active
|
||||
const isDarkMode = resolvedTheme === 'dark'
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col p-8 overflow-y-auto">
|
||||
<div className="max-w-2xl w-full mx-auto">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<h2 className={`text-2xl font-medium ${isDarkMode ? "text-white" : "text-zinc-900"} mb-2`}>
|
||||
<h2
|
||||
className={`text-2xl font-medium ${isDarkMode ? 'text-white' : 'text-zinc-900'} mb-2`}
|
||||
>
|
||||
Connect
|
||||
</h2>
|
||||
<p className="text-zinc-500 mb-6">
|
||||
Connect and import a GitHub repo or start from a template
|
||||
</p>
|
||||
|
||||
|
||||
{/* GitHub Account Selector - Only show if multiple accounts */}
|
||||
{clerk.user?.externalAccounts && clerk.user.externalAccounts.length > 1 && (
|
||||
<div className="flex items-center justify-center mb-6">
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-zinc-100 dark:bg-zinc-800 rounded-md cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-700">
|
||||
<Github className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">
|
||||
{clerk.user?.externalAccounts?.find(acc => acc.provider === 'github')?.username || 'git-account'}
|
||||
</span>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
{clerk.user?.externalAccounts &&
|
||||
clerk.user.externalAccounts.length > 1 && (
|
||||
<div className="flex items-center justify-center mb-6">
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-zinc-100 dark:bg-zinc-800 rounded-md cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-700">
|
||||
<Github className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">
|
||||
{clerk.user?.externalAccounts?.find(
|
||||
(acc: any) => acc.provider === 'github'
|
||||
)?.username || 'git-account'}
|
||||
</span>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Authentication Warning - Only show if not fully authenticated */}
|
||||
{!isFullyAuthenticated && (
|
||||
<Collapsible open={showAuthWarning} onOpenChange={setShowAuthWarning}>
|
||||
@@ -224,7 +250,10 @@ export function ConnectStep() {
|
||||
<Alert className="mb-6 cursor-pointer hover:bg-amber-50 dark:hover:bg-amber-950/20">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription className="flex items-center justify-between w-full">
|
||||
<span>Authentication required to continue ({progress.completed}/{progress.total} complete)</span>
|
||||
<span>
|
||||
Authentication required to continue ({progress.completed}/
|
||||
{progress.total} complete)
|
||||
</span>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -236,7 +265,9 @@ export function ConnectStep() {
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Github className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">Sign in with Clerk</span>
|
||||
<span className="text-sm font-medium">
|
||||
Sign in with Clerk
|
||||
</span>
|
||||
</div>
|
||||
<div className="scale-90 origin-top-left">
|
||||
<SignIn routing="hash" />
|
||||
@@ -244,28 +275,36 @@ export function ConnectStep() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
{missing.clerkGithub && !missing.clerkSignIn && (
|
||||
<Card className="border-amber-200 bg-amber-50/50 dark:bg-amber-950/20">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Github className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">Connect GitHub Account</span>
|
||||
<span className="text-sm font-medium">
|
||||
Connect GitHub Account
|
||||
</span>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" onClick={() => window.open('/user-profile', '_blank')}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => window.open('/user-profile', '_blank')}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 mr-2" />
|
||||
Connect GitHub
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
{missing.walletConnection && (
|
||||
<Card className="border-amber-200 bg-amber-50/50 dark:bg-amber-950/20">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Wallet className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">Connect Wallet</span>
|
||||
<span className="text-sm font-medium">
|
||||
Connect Wallet
|
||||
</span>
|
||||
</div>
|
||||
<Button size="sm" onClick={handleConnectWallet}>
|
||||
Connect Wallet
|
||||
@@ -273,46 +312,54 @@ export function ConnectStep() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{missing.githubBackendSync && !missing.walletConnection && !missing.clerkGithub && (
|
||||
<Card className="border-amber-200 bg-amber-50/50 dark:bg-amber-950/20">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Github className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">Sync GitHub Access</span>
|
||||
</div>
|
||||
<GitHubBackendAuth onAuthStatusChange={handleGithubAuthChange} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{missing.githubBackendSync &&
|
||||
!missing.walletConnection &&
|
||||
!missing.clerkGithub && (
|
||||
<Card className="border-amber-200 bg-amber-50/50 dark:bg-amber-950/20">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Github className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">
|
||||
Sync GitHub Access
|
||||
</span>
|
||||
</div>
|
||||
<GitHubBackendAuth
|
||||
onAuthStatusChange={handleGithubAuthChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
)}
|
||||
|
||||
|
||||
{/* Mode Selection Tabs */}
|
||||
<div className="grid grid-cols-2 gap-1 p-1 bg-zinc-100 dark:bg-zinc-800 rounded-lg mb-6">
|
||||
<Button
|
||||
variant={isImportMode ? "default" : "ghost"}
|
||||
className={`${isImportMode
|
||||
? 'bg-white dark:bg-zinc-700 shadow-sm'
|
||||
: 'bg-transparent hover:bg-white/50 dark:hover:bg-zinc-700/50'
|
||||
variant={isImportMode ? 'default' : 'ghost'}
|
||||
className={`${
|
||||
isImportMode
|
||||
? 'bg-white dark:bg-zinc-700 shadow-sm'
|
||||
: 'bg-transparent hover:bg-white/50 dark:hover:bg-zinc-700/50'
|
||||
}`}
|
||||
onClick={() => toggleMode('import')}
|
||||
>
|
||||
Import a repository
|
||||
</Button>
|
||||
<Button
|
||||
variant={!isImportMode ? "default" : "ghost"}
|
||||
className={`${!isImportMode
|
||||
? 'bg-white dark:bg-zinc-700 shadow-sm'
|
||||
: 'bg-transparent hover:bg-white/50 dark:hover:bg-zinc-700/50'
|
||||
variant={!isImportMode ? 'default' : 'ghost'}
|
||||
className={`${
|
||||
!isImportMode
|
||||
? 'bg-white dark:bg-zinc-700 shadow-sm'
|
||||
: 'bg-transparent hover:bg-white/50 dark:hover:bg-zinc-700/50'
|
||||
}`}
|
||||
onClick={() => toggleMode('template')}
|
||||
>
|
||||
Start with a template
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Content Area */}
|
||||
{isImportMode ? (
|
||||
/* Repository Selection */
|
||||
@@ -327,7 +374,8 @@ export function ConnectStep() {
|
||||
<Alert>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
No repositories found. Make sure your GitHub account has repositories.
|
||||
No repositories found. Make sure your GitHub account has
|
||||
repositories.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
@@ -335,20 +383,24 @@ export function ConnectStep() {
|
||||
<>
|
||||
<div className="space-y-2 max-h-60 overflow-y-auto">
|
||||
{repositories.map((repo: Repository) => (
|
||||
<div
|
||||
key={repo.id}
|
||||
<div
|
||||
key={repo.id}
|
||||
className={`flex items-center p-4 rounded-lg border cursor-pointer transition-all ${
|
||||
selectedRepo === repo.full_name
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950/20'
|
||||
selectedRepo === repo.full_name
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950/20'
|
||||
: 'border-zinc-200 dark:border-zinc-700 hover:border-zinc-300 dark:hover:border-zinc-600'
|
||||
}`}
|
||||
onClick={() => handleRepoSelect(repo.full_name)}
|
||||
>
|
||||
<Github className="h-5 w-5 mr-3 text-zinc-500 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm">{repo.full_name}</div>
|
||||
<div className="font-medium text-sm">
|
||||
{repo.full_name}
|
||||
</div>
|
||||
{repo.description && (
|
||||
<div className="text-xs text-zinc-500 truncate">{repo.description}</div>
|
||||
<div className="text-xs text-zinc-500 truncate">
|
||||
{repo.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{selectedRepo === repo.full_name && (
|
||||
@@ -357,11 +409,14 @@ export function ConnectStep() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Project Name Input for Repository Import */}
|
||||
{selectedRepo && (
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label htmlFor="projectName" className="text-sm font-medium">
|
||||
<Label
|
||||
htmlFor="projectName"
|
||||
className="text-sm font-medium"
|
||||
>
|
||||
Project Name
|
||||
</Label>
|
||||
<Input
|
||||
@@ -382,39 +437,47 @@ export function ConnectStep() {
|
||||
) : (
|
||||
/* Template Selection */
|
||||
<div className="space-y-4">
|
||||
{AVAILABLE_TEMPLATES.filter(t => !t.isComingSoon).map((template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
className={`flex items-center p-4 rounded-lg border cursor-pointer transition-all ${
|
||||
selectedTemplate?.id === template.id
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950/20'
|
||||
: 'border-zinc-200 dark:border-zinc-700 hover:border-zinc-300 dark:hover:border-zinc-600'
|
||||
}`}
|
||||
onClick={() => handleTemplateSelect(template)}
|
||||
>
|
||||
{/* Template Icon */}
|
||||
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-zinc-100 dark:bg-zinc-800 mr-4">
|
||||
<div className="w-6 h-6 bg-zinc-600 dark:bg-zinc-400 rounded flex items-center justify-center text-xs font-bold text-white">
|
||||
{template.icon === 'web' ? 'PWA' : template.icon === 'nextjs' ? 'N' : 'IMG'}
|
||||
{AVAILABLE_TEMPLATES.filter((t) => !t.isComingSoon).map(
|
||||
(template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
className={`flex items-center p-4 rounded-lg border cursor-pointer transition-all ${
|
||||
selectedTemplate?.id === template.id
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950/20'
|
||||
: 'border-zinc-200 dark:border-zinc-700 hover:border-zinc-300 dark:hover:border-zinc-600'
|
||||
}`}
|
||||
onClick={() => handleTemplateSelect(template)}
|
||||
>
|
||||
{/* Template Icon */}
|
||||
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-zinc-100 dark:bg-zinc-800 mr-4">
|
||||
<div className="w-6 h-6 bg-zinc-600 dark:bg-zinc-400 rounded flex items-center justify-center text-xs font-bold text-white">
|
||||
{template.icon === 'web'
|
||||
? 'PWA'
|
||||
: template.icon === 'nextjs'
|
||||
? 'N'
|
||||
: 'IMG'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Template Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm mb-1">{template.name}</div>
|
||||
<div className="flex items-center text-xs text-zinc-500">
|
||||
<Github className="h-3 w-3 mr-1" />
|
||||
{template.repoFullName}
|
||||
|
||||
{/* Template Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm mb-1">
|
||||
{template.name}
|
||||
</div>
|
||||
<div className="flex items-center text-xs text-zinc-500">
|
||||
<Github className="h-3 w-3 mr-1" />
|
||||
{template.repoFullName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Selection Indicator */}
|
||||
{selectedTemplate?.id === template.id && (
|
||||
<CheckCircle2 className="h-5 w-5 text-blue-500 flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Selection Indicator */}
|
||||
{selectedTemplate?.id === template.id && (
|
||||
<CheckCircle2 className="h-5 w-5 text-blue-500 flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Project Name Input for Templates */}
|
||||
{selectedTemplate && (
|
||||
<div className="mt-6 space-y-2">
|
||||
@@ -435,15 +498,20 @@ export function ConnectStep() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between items-center mt-8">
|
||||
<Button variant="outline" disabled>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
onClick={handleNext}
|
||||
disabled={!isFullyAuthenticated || (isImportMode ? !selectedRepo : (!selectedTemplate || !projectName.trim()))}
|
||||
disabled={
|
||||
!isFullyAuthenticated ||
|
||||
(isImportMode
|
||||
? !selectedRepo
|
||||
: !selectedTemplate || !projectName.trim())
|
||||
}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
@@ -451,4 +519,4 @@ export function ConnectStep() {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ export const ConnectStep = () => {
|
||||
<StepHeader
|
||||
title="Connect"
|
||||
description="Connect and import a GitHub repo or start from a template"
|
||||
icon={<GitBranch />}
|
||||
/>
|
||||
|
||||
{/* Git account selector */}
|
||||
|
||||
@@ -1,46 +1,57 @@
|
||||
// src/components/onboarding/deploy-step/deploy-step.tsx
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { Github, Loader2, AlertTriangle, CheckCircle2 } from 'lucide-react'
|
||||
import { useOnboarding } from '@/components/onboarding/useOnboarding'
|
||||
import { useGQLClient } from '@/context'
|
||||
import { useWallet } from '@/context/WalletContext'
|
||||
import { useDeployment } from '@/hooks/useDeployment'
|
||||
import { useTemplateDeployment } from '@/hooks/useTemplate'
|
||||
import { Button } from '@workspace/ui/components/button'
|
||||
import { Progress } from '@workspace/ui/components/progress'
|
||||
import { Dialog, DialogContent, DialogTitle, DialogDescription, DialogFooter } from '@workspace/ui/components/dialog'
|
||||
import { Alert, AlertDescription } from '@workspace/ui/components/alert'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card'
|
||||
import { Badge } from '@workspace/ui/components/badge'
|
||||
import { Button } from '@workspace/ui/components/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '@workspace/ui/components/card'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogTitle
|
||||
} from '@workspace/ui/components/dialog'
|
||||
import { Progress } from '@workspace/ui/components/progress'
|
||||
import { AlertTriangle, CheckCircle2, Github, Loader2 } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export function DeployStep() {
|
||||
const { previousStep, nextStep, formData, setFormData } = useOnboarding()
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
|
||||
// State
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false)
|
||||
const [deploymentError, setDeploymentError] = useState<string | null>(null)
|
||||
const [deploymentSuccess, setDeploymentSuccess] = useState(false)
|
||||
|
||||
|
||||
// Contexts and hooks
|
||||
const { wallet } = useWallet()
|
||||
const { deployRepository, isDeploying: isRepoDeploying } = useDeployment()
|
||||
const { deployTemplate, isDeploying: isTemplateDeploying } = useTemplateDeployment()
|
||||
|
||||
const { deployTemplate, isDeploying: isTemplateDeploying } =
|
||||
useTemplateDeployment()
|
||||
|
||||
// Determine deployment type and get the right deploying state
|
||||
const isTemplateMode = formData.deploymentMode === 'template'
|
||||
const isDeploying = isTemplateMode ? isTemplateDeploying : isRepoDeploying
|
||||
|
||||
|
||||
// Handle hydration mismatch by waiting for mount
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
|
||||
// Get deployment info
|
||||
const getDeploymentInfo = () => {
|
||||
if (isTemplateMode) {
|
||||
@@ -54,50 +65,53 @@ export function DeployStep() {
|
||||
return {
|
||||
name: formData.githubRepo?.split('/').pop() || 'Repository',
|
||||
source: formData.githubRepo || 'Unknown Repository',
|
||||
projectName: formData.projectName || formData.githubRepo?.split('/').pop() || 'New Project',
|
||||
projectName:
|
||||
formData.projectName ||
|
||||
formData.githubRepo?.split('/').pop() ||
|
||||
'New Project',
|
||||
type: 'Repository'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const deploymentInfo = getDeploymentInfo()
|
||||
|
||||
|
||||
// Open the confirmation modal
|
||||
const handlePayAndDeploy = () => {
|
||||
if (!wallet?.address) {
|
||||
toast.error('Wallet not connected')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (!formData.selectedOrg) {
|
||||
toast.error('No organization selected')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (isTemplateMode && (!formData.template || !formData.projectName)) {
|
||||
toast.error('Template or project name missing')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (!isTemplateMode && !formData.githubRepo) {
|
||||
toast.error('Repository not selected')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
setShowConfirmDialog(true)
|
||||
}
|
||||
|
||||
|
||||
// Close the confirmation modal
|
||||
const handleCancelConfirm = () => {
|
||||
setShowConfirmDialog(false)
|
||||
}
|
||||
|
||||
|
||||
// Handle confirmed deployment
|
||||
const handleConfirmDeploy = async () => {
|
||||
setShowConfirmDialog(false)
|
||||
setDeploymentError(null)
|
||||
setDeploymentSuccess(false)
|
||||
|
||||
|
||||
try {
|
||||
if (isTemplateMode) {
|
||||
await deployTemplateProject()
|
||||
@@ -106,28 +120,31 @@ export function DeployStep() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Deployment failed:', error)
|
||||
setDeploymentError(error instanceof Error ? error.message : 'Deployment failed')
|
||||
setDeploymentError(
|
||||
error instanceof Error ? error.message : 'Deployment failed'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Deploy template project
|
||||
const deployTemplateProject = async () => {
|
||||
if (!formData.template || !formData.projectName || !formData.selectedOrg) {
|
||||
throw new Error('Missing required template deployment data')
|
||||
}
|
||||
|
||||
|
||||
const config = {
|
||||
template: formData.template,
|
||||
template: {
|
||||
...formData.template,
|
||||
id: formData.template?.id || '',
|
||||
icon: formData.template?.icon || ''
|
||||
},
|
||||
projectName: formData.projectName,
|
||||
organizationSlug: formData.selectedOrg,
|
||||
environmentVariables: formData.environmentVariables || [],
|
||||
deployerLrn: formData.selectedLrn
|
||||
}
|
||||
|
||||
console.log('Deploying template with config:', config)
|
||||
|
||||
const result = await deployTemplate(config)
|
||||
|
||||
|
||||
// Save deployment results
|
||||
setFormData({
|
||||
deploymentId: result.deploymentId,
|
||||
@@ -135,85 +152,136 @@ export function DeployStep() {
|
||||
projectId: result.projectId,
|
||||
repositoryUrl: result.repositoryUrl
|
||||
})
|
||||
|
||||
|
||||
setDeploymentSuccess(true)
|
||||
toast.success('Template deployed successfully!')
|
||||
|
||||
|
||||
// Move to success step after short delay
|
||||
setTimeout(() => {
|
||||
nextStep()
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
|
||||
// Deploy repository project
|
||||
const deployRepositoryProject = async () => {
|
||||
if (!formData.githubRepo || !formData.selectedOrg) {
|
||||
throw new Error('Missing required repository deployment data')
|
||||
}
|
||||
|
||||
|
||||
const config = {
|
||||
projectId: '', // Will be generated by backend
|
||||
organizationSlug: formData.selectedOrg,
|
||||
repository: formData.githubRepo,
|
||||
branch: 'main', // Default branch
|
||||
name: formData.projectName || formData.githubRepo.split('/').pop() || 'New Project',
|
||||
name:
|
||||
formData.projectName ||
|
||||
formData.githubRepo.split('/').pop() ||
|
||||
'New Project',
|
||||
environmentVariables: formData.environmentVariables || []
|
||||
}
|
||||
|
||||
|
||||
console.log('Deploying repository with config:', config)
|
||||
|
||||
|
||||
const result = await deployRepository(config)
|
||||
|
||||
|
||||
// Save deployment results
|
||||
setFormData({
|
||||
deploymentId: result.id,
|
||||
deploymentUrl: result.url,
|
||||
projectId: result.id
|
||||
})
|
||||
|
||||
|
||||
setDeploymentSuccess(true)
|
||||
toast.success('Repository deployed successfully!')
|
||||
|
||||
|
||||
// Move to success step after short delay
|
||||
setTimeout(() => {
|
||||
nextStep()
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
|
||||
// Don't render UI until after mount to prevent hydration mismatch
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
// Determine if dark mode is active
|
||||
const isDarkMode = resolvedTheme === 'dark'
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full h-full flex flex-col items-center justify-center p-8">
|
||||
<div className="max-w-md w-full mx-auto">
|
||||
{/* Deploy icon */}
|
||||
<div className="mx-auto mb-6 flex justify-center">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={isDarkMode ? "text-white" : "text-black"}>
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<polyline points="7.5 4.21 12 6.81 16.5 4.21" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<polyline points="7.5 19.79 7.5 14.6 3 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<polyline points="21 12 16.5 14.6 16.5 19.79" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={isDarkMode ? 'text-white' : 'text-black'}
|
||||
>
|
||||
<path
|
||||
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<polyline
|
||||
points="7.5 4.21 12 6.81 16.5 4.21"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<polyline
|
||||
points="7.5 19.79 7.5 14.6 3 12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<polyline
|
||||
points="21 12 16.5 14.6 16.5 19.79"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<polyline
|
||||
points="3.27 6.96 12 12.01 20.73 6.96"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
y1="22.08"
|
||||
x2="12"
|
||||
y2="12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Deploy header */}
|
||||
<h2 className={`text-2xl font-medium ${isDarkMode ? "text-white" : "text-zinc-900"} text-center mb-2`}>
|
||||
<h2
|
||||
className={`text-2xl font-medium ${isDarkMode ? 'text-white' : 'text-zinc-900'} text-center mb-2`}
|
||||
>
|
||||
{isDeploying ? 'Deploying...' : 'Deploy'}
|
||||
</h2>
|
||||
<p className="text-center text-zinc-500 mb-8">
|
||||
{isDeploying
|
||||
? 'Your project is being deployed. This may take a few minutes.'
|
||||
{isDeploying
|
||||
? 'Your project is being deployed. This may take a few minutes.'
|
||||
: 'Review and confirm deployment'}
|
||||
</p>
|
||||
|
||||
|
||||
{/* Deployment Summary */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader className="pb-3">
|
||||
@@ -226,13 +294,15 @@ export function DeployStep() {
|
||||
<div className="flex items-center gap-3">
|
||||
<Github className="h-4 w-4 text-muted-foreground" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm">{deploymentInfo.projectName}</div>
|
||||
<div className="font-medium text-sm">
|
||||
{deploymentInfo.projectName}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground font-mono">
|
||||
{deploymentInfo.source}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-2 text-xs">
|
||||
<div>
|
||||
<div className="text-muted-foreground">Organization</div>
|
||||
@@ -240,21 +310,28 @@ export function DeployStep() {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">Deployer</div>
|
||||
<div className="font-medium">{formData.selectedLrn ? 'LRN' : 'Auction'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{formData.environmentVariables && formData.environmentVariables.length > 0 && (
|
||||
<div className="pt-2">
|
||||
<div className="text-xs text-muted-foreground mb-1">Environment Variables</div>
|
||||
<div className="text-xs">
|
||||
{formData.environmentVariables.length} variable{formData.environmentVariables.length !== 1 ? 's' : ''} configured
|
||||
<div className="font-medium">
|
||||
{formData.selectedLrn ? 'LRN' : 'Auction'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{formData.environmentVariables &&
|
||||
formData.environmentVariables.length > 0 && (
|
||||
<div className="pt-2">
|
||||
<div className="text-xs text-muted-foreground mb-1">
|
||||
Environment Variables
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
{formData.environmentVariables.length} variable
|
||||
{formData.environmentVariables.length !== 1 ? 's' : ''}{' '}
|
||||
configured
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
{/* Error Display */}
|
||||
{deploymentError && (
|
||||
<Alert className="mb-6" variant="destructive">
|
||||
@@ -265,7 +342,7 @@ export function DeployStep() {
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
|
||||
{/* Success Display */}
|
||||
{deploymentSuccess && (
|
||||
<Alert className="mb-6">
|
||||
@@ -273,40 +350,49 @@ export function DeployStep() {
|
||||
<AlertDescription>
|
||||
<div className="font-medium">Deployment Successful!</div>
|
||||
<div className="text-sm mt-1">
|
||||
Your project has been deployed successfully. You'll be redirected to the project dashboard.
|
||||
Your project has been deployed successfully. You'll be
|
||||
redirected to the project dashboard.
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
|
||||
{/* Deployment Progress - Only show while deploying */}
|
||||
{isDeploying && (
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className={`${isDarkMode ? "text-white" : "text-zinc-900"} text-sm`}>
|
||||
{isTemplateMode ? 'Creating repository from template...' : 'Deploying repository...'}
|
||||
<div
|
||||
className={`${isDarkMode ? 'text-white' : 'text-zinc-900'} text-sm`}
|
||||
>
|
||||
{isTemplateMode
|
||||
? 'Creating repository from template...'
|
||||
: 'Deploying repository...'}
|
||||
</div>
|
||||
</div>
|
||||
<Progress value={undefined} className={`h-2 ${isDarkMode ? "bg-zinc-800" : "bg-zinc-200"}`} />
|
||||
<Progress
|
||||
value={undefined}
|
||||
className={`h-2 ${isDarkMode ? 'bg-zinc-800' : 'bg-zinc-200'}`}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground mt-2">
|
||||
This process may take several minutes. Please do not close this window.
|
||||
This process may take several minutes. Please do not close this
|
||||
window.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Navigation buttons */}
|
||||
<div className="flex justify-between items-center mt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
className={`${isDarkMode ? "text-zinc-400 border-zinc-700" : "text-zinc-600 border-zinc-300"} bg-transparent`}
|
||||
<Button
|
||||
variant="outline"
|
||||
className={`${isDarkMode ? 'text-zinc-400 border-zinc-700' : 'text-zinc-600 border-zinc-300'} bg-transparent`}
|
||||
onClick={previousStep}
|
||||
disabled={isDeploying || deploymentSuccess}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
|
||||
|
||||
{deploymentSuccess ? (
|
||||
<Button
|
||||
<Button
|
||||
className="bg-green-600 hover:bg-green-700 text-white"
|
||||
onClick={nextStep}
|
||||
>
|
||||
@@ -314,29 +400,42 @@ export function DeployStep() {
|
||||
Continue
|
||||
</Button>
|
||||
) : isDeploying ? (
|
||||
<Button
|
||||
className={`${isDarkMode ? "bg-zinc-700 text-zinc-300" : "bg-zinc-300 text-zinc-600"}`}
|
||||
<Button
|
||||
className={`${isDarkMode ? 'bg-zinc-700 text-zinc-300' : 'bg-zinc-300 text-zinc-600'}`}
|
||||
disabled
|
||||
>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Deploying...
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
<Button
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white flex items-center"
|
||||
onClick={handlePayAndDeploy}
|
||||
disabled={deploymentError !== null}
|
||||
>
|
||||
{formData.deploymentType === 'auction' ? 'Pay and Deploy' : 'Deploy'}
|
||||
<svg className="ml-2 h-4 w-4" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 12H19M19 12L13 6M19 12L13 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
{formData.deploymentType === 'auction'
|
||||
? 'Pay and Deploy'
|
||||
: 'Deploy'}
|
||||
<svg
|
||||
className="ml-2 h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 12H19M19 12L13 6M19 12L13 18"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Transaction Confirmation Dialog */}
|
||||
<Dialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
||||
<DialogContent className="bg-background border max-w-md">
|
||||
@@ -344,7 +443,7 @@ export function DeployStep() {
|
||||
<DialogDescription>
|
||||
Review the deployment details before proceeding.
|
||||
</DialogDescription>
|
||||
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
{/* Project Info */}
|
||||
<div className="space-y-2">
|
||||
@@ -360,11 +459,13 @@ export function DeployStep() {
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Source:</span>
|
||||
<span className="font-mono text-xs">{deploymentInfo.source}</span>
|
||||
<span className="font-mono text-xs">
|
||||
{deploymentInfo.source}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Wallet Info */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium">Payment Address</h3>
|
||||
@@ -372,17 +473,19 @@ export function DeployStep() {
|
||||
{wallet?.address}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Deployer Info */}
|
||||
{formData.selectedLrn && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium">Deployer</h3>
|
||||
<div className="text-sm">
|
||||
<div className="font-mono text-xs">{formData.selectedLrn}</div>
|
||||
<div className="font-mono text-xs">
|
||||
{formData.selectedLrn}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Cost Info */}
|
||||
{formData.deploymentType === 'auction' && (
|
||||
<div className="space-y-2">
|
||||
@@ -400,22 +503,15 @@ export function DeployStep() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<DialogFooter className="flex justify-end space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancelConfirm}
|
||||
>
|
||||
<Button variant="outline" onClick={handleCancelConfirm}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirmDeploy}
|
||||
>
|
||||
Confirm Deployment
|
||||
</Button>
|
||||
<Button onClick={handleConfirmDeploy}>Confirm Deployment</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,7 @@
|
||||
|
||||
// Main component
|
||||
export { default as Onboarding } from './Onboarding'
|
||||
export {
|
||||
default as OnboardingDialog,
|
||||
hasCompletedOnboarding
|
||||
} from './OnboardingDialog'
|
||||
export { default as OnboardingDialog } from './OnboardingDialog'
|
||||
|
||||
// Step components
|
||||
export { ConfigureStep } from './configure-step'
|
||||
@@ -28,7 +25,7 @@ export * from './common'
|
||||
export * from './sidebar'
|
||||
|
||||
// Store and hooks
|
||||
export { useOnboarding } from './store'
|
||||
export { useOnboarding } from './useOnboarding'
|
||||
|
||||
// Types
|
||||
export * from './types'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/**
|
||||
* Available steps in the onboarding flow
|
||||
*/
|
||||
export type Step = 'connect' | 'configure' | 'deploy'
|
||||
export type Step = 'connect' | 'configure' | 'deploy' | 'success'
|
||||
|
||||
/**
|
||||
* Form data collected during the onboarding process
|
||||
|
||||
@@ -1,86 +1,120 @@
|
||||
'use client'
|
||||
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
// src/components/onboarding/useOnboarding.ts
|
||||
import { useState } from 'react'
|
||||
import type {
|
||||
OnboardingFormData} from '@/types'
|
||||
|
||||
// Step type for navigation
|
||||
export type Step = 'connect' | 'configure' | 'deploy' | 'success'
|
||||
|
||||
export interface EnvironmentVariables {
|
||||
key: string
|
||||
value: string
|
||||
// Initial form data
|
||||
const initialFormData: OnboardingFormData = {
|
||||
// Step 1: Connect
|
||||
githubRepo: undefined,
|
||||
template: undefined,
|
||||
projectName: undefined,
|
||||
deploymentMode: undefined,
|
||||
|
||||
// Step 2: Configure
|
||||
selectedLrn: undefined,
|
||||
selectedOrg: undefined, // Add this property
|
||||
environmentVariables: [], // Use array type
|
||||
deploymentType: undefined, // Add missing properties
|
||||
deployerCount: undefined,
|
||||
maxPrice: undefined,
|
||||
|
||||
// Step 3: Deploy
|
||||
deploymentId: undefined,
|
||||
repositoryUrl: undefined,
|
||||
deploymentUrl: undefined, // Add missing property
|
||||
projectId: undefined // Add missing property
|
||||
}
|
||||
|
||||
export interface OnboardingFormData {
|
||||
// Connect step
|
||||
githubRepo?: string
|
||||
export function useOnboarding() {
|
||||
const [formData, setFormData] = useState<OnboardingFormData>(initialFormData)
|
||||
const [currentStep, setCurrentStep] = useState<Step>('connect')
|
||||
|
||||
// Configure step
|
||||
deploymentType?: 'auction' | 'lrn'
|
||||
deployerCount?: string
|
||||
maxPrice?: string
|
||||
selectedLrn?: string
|
||||
environments?: {
|
||||
production: boolean
|
||||
preview: boolean
|
||||
development: boolean
|
||||
// Update form data (partial update)
|
||||
const updateFormData = (updates: Partial<OnboardingFormData>) => {
|
||||
setFormData(prev => ({ ...prev, ...updates }))
|
||||
}
|
||||
environmentVariables?: Record<string, string>
|
||||
|
||||
// Deploy step
|
||||
deploymentId?: string
|
||||
deploymentUrl?: string
|
||||
// Reset form data
|
||||
const resetFormData = () => {
|
||||
setFormData(initialFormData)
|
||||
setCurrentStep('connect')
|
||||
}
|
||||
|
||||
// Success step
|
||||
projectId?: string
|
||||
}
|
||||
|
||||
interface OnboardingState {
|
||||
currentStep: Step
|
||||
formData: OnboardingFormData
|
||||
setCurrentStep: (step: Step) => void
|
||||
setFormData: (data: Partial<OnboardingFormData>) => void
|
||||
nextStep: () => void
|
||||
previousStep: () => void
|
||||
resetOnboarding: () => void
|
||||
}
|
||||
|
||||
const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy', 'success']
|
||||
|
||||
export const useOnboarding = create<OnboardingState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
currentStep: 'connect',
|
||||
formData: {},
|
||||
|
||||
setCurrentStep: (step) => set({ currentStep: step }),
|
||||
|
||||
setFormData: (data) =>
|
||||
set((state) => ({
|
||||
formData: { ...state.formData, ...data }
|
||||
})),
|
||||
|
||||
nextStep: () =>
|
||||
set((state) => {
|
||||
const currentIndex = STEP_ORDER.indexOf(state.currentStep)
|
||||
const nextStep = STEP_ORDER[currentIndex + 1]
|
||||
return nextStep ? { currentStep: nextStep } : state
|
||||
}),
|
||||
|
||||
previousStep: () =>
|
||||
set((state) => {
|
||||
const currentIndex = STEP_ORDER.indexOf(state.currentStep)
|
||||
const previousStep = STEP_ORDER[currentIndex - 1]
|
||||
return previousStep ? { currentStep: previousStep } : state
|
||||
}),
|
||||
|
||||
resetOnboarding: () =>
|
||||
set({
|
||||
currentStep: 'connect',
|
||||
formData: {}
|
||||
})
|
||||
}),
|
||||
{
|
||||
name: 'laconic-onboarding-storage'
|
||||
// Step navigation
|
||||
const nextStep = () => {
|
||||
switch (currentStep) {
|
||||
case 'connect':
|
||||
setCurrentStep('configure')
|
||||
break
|
||||
case 'configure':
|
||||
setCurrentStep('deploy')
|
||||
break
|
||||
case 'deploy':
|
||||
setCurrentStep('success')
|
||||
break
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const previousStep = () => {
|
||||
switch (currentStep) {
|
||||
case 'configure':
|
||||
setCurrentStep('connect')
|
||||
break
|
||||
case 'deploy':
|
||||
setCurrentStep('configure')
|
||||
break
|
||||
case 'success':
|
||||
setCurrentStep('deploy')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Validation helpers
|
||||
const validateStep1 = () => {
|
||||
if (formData.deploymentMode === 'template') {
|
||||
return !!(formData.template && formData.projectName)
|
||||
}
|
||||
return !!formData.githubRepo
|
||||
}
|
||||
|
||||
const validateStep2 = () => {
|
||||
return !!(formData.selectedLrn && formData.selectedOrg)
|
||||
}
|
||||
|
||||
const validateStep3 = () => {
|
||||
return !!formData.deploymentId
|
||||
}
|
||||
|
||||
return {
|
||||
// Data
|
||||
formData,
|
||||
currentStep,
|
||||
|
||||
// Actions
|
||||
setFormData: updateFormData,
|
||||
setCurrentStep,
|
||||
resetFormData,
|
||||
resetOnboarding: resetFormData, // Alias for compatibility
|
||||
nextStep,
|
||||
previousStep,
|
||||
|
||||
// Validation
|
||||
validateStep1,
|
||||
validateStep2,
|
||||
validateStep3,
|
||||
|
||||
// Store-like interface for compatibility
|
||||
getState: () => ({
|
||||
formData,
|
||||
currentStep,
|
||||
nextStep,
|
||||
previousStep,
|
||||
setFormData: updateFormData,
|
||||
resetFormData
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,9 @@
|
||||
export interface TemplateDetail {
|
||||
id: string
|
||||
name: string
|
||||
icon: string
|
||||
icon: string // Required string
|
||||
repoFullName: string
|
||||
description: string
|
||||
description: string // Required string
|
||||
isComingSoon?: boolean
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export const BackendProvider: React.FC<{ children: ReactNode }> = ({
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
// Check backend connection
|
||||
const checkBackendConnection = useCallback(async () => {
|
||||
const checkBackendConnection = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:8000/auth/session', {
|
||||
method: 'GET',
|
||||
@@ -59,11 +59,11 @@ export const BackendProvider: React.FC<{ children: ReactNode }> = ({
|
||||
console.log('❌ Backend not connected')
|
||||
}
|
||||
|
||||
return connected
|
||||
// Don't return anything - function returns Promise<void>
|
||||
} catch (error) {
|
||||
console.error('Error checking backend connection:', error)
|
||||
setIsBackendConnected(false)
|
||||
return false
|
||||
// Don't return anything - function returns Promise<void>
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ export function useAuthStatus(): AuthStatus & AuthActions {
|
||||
// Calculate what's missing
|
||||
const missing = {
|
||||
clerkSignIn: !isSignedIn,
|
||||
clerkGithub: isSignedIn && !hasGithubInClerk,
|
||||
clerkGithub: (isSignedIn ?? false) && !hasGithubInClerk,
|
||||
walletConnection: !hasWalletAddress, // Just need wallet address for this step
|
||||
backendConnection: hasWalletAddress && !isWalletSessionActive, // Need SIWE auth for backend
|
||||
githubBackendSync: isBackendConnected && !isGithubBackendAuth
|
||||
@@ -177,7 +177,7 @@ export function useAuthStatus(): AuthStatus & AuthActions {
|
||||
return {
|
||||
// Individual systems
|
||||
clerk: {
|
||||
isSignedIn,
|
||||
isSignedIn: isSignedIn ?? false,
|
||||
isLoaded: isClerkLoaded && isUserLoaded,
|
||||
hasGithubConnected: hasGithubInClerk,
|
||||
user
|
||||
|
||||
@@ -37,13 +37,13 @@ export function useRepoData(repoId: string): UseRepoDataReturn {
|
||||
try {
|
||||
// Check if user has connected GitHub account
|
||||
const githubAccount = user.externalAccounts.find(
|
||||
account => account.provider === 'oauth_github'
|
||||
account => account.provider === 'github'
|
||||
);
|
||||
|
||||
if (githubAccount) {
|
||||
// Try to get GitHub OAuth token from Clerk
|
||||
try {
|
||||
token = await user.getToken({ template: 'github' });
|
||||
// token = await user.getToken({ template: 'github' });
|
||||
console.log('Using GitHub token from Clerk');
|
||||
} catch (err) {
|
||||
console.error('Error getting GitHub token from Clerk:', err);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// src/hooks/useRepoSelection.ts
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useOctokit } from '@/context/OctokitContext'
|
||||
import { adaptRepositories, type GitHubRepo } from '../utils/typeAdapters';
|
||||
|
||||
interface Repository {
|
||||
id: string;
|
||||
@@ -58,7 +59,7 @@ export function useRepoSelection(): UseRepoSelectionResult {
|
||||
}
|
||||
}));
|
||||
|
||||
setRepositories(repoData);
|
||||
setRepositories(adaptRepositories(repoData as GitHubRepo[]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch repositories:', err);
|
||||
@@ -89,6 +90,9 @@ export function useRepoSelection(): UseRepoSelectionResult {
|
||||
try {
|
||||
if (isAuth && octokit) {
|
||||
const [owner, repo] = repoFullName.split('/');
|
||||
if (!owner || !repo) {
|
||||
throw new Error('Invalid repository format');
|
||||
}
|
||||
|
||||
const response = await octokit.request('GET /repos/{owner}/{repo}/branches', {
|
||||
owner,
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface Deployment {
|
||||
status: string
|
||||
isCurrent: boolean
|
||||
createdAt: string | number
|
||||
commit?: string
|
||||
createdBy?: {
|
||||
name: string
|
||||
}
|
||||
@@ -18,4 +19,17 @@ export interface Domain {
|
||||
branch: string
|
||||
status: string
|
||||
createdAt: string | number
|
||||
isCustom: boolean
|
||||
}
|
||||
|
||||
// Add missing types that are referenced in components
|
||||
export interface EnvVarItem {
|
||||
key?: string
|
||||
value?: string
|
||||
environments?: string[]
|
||||
isEditing?: boolean
|
||||
}
|
||||
|
||||
export type EditableEnvVarItem = Partial<EnvVarItem> & {
|
||||
isEditing?: boolean
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
// Page action type (for header actions)
|
||||
export interface PageAction {
|
||||
label: string
|
||||
href: string
|
||||
icon?: string | LucideIcon // Add missing icon property
|
||||
external?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
// Page header configuration
|
||||
export interface PageHeaderProps {
|
||||
title: string
|
||||
description?: string // Add missing description property
|
||||
actions?: PageAction[]
|
||||
breadcrumbs?: Array<{
|
||||
label: string
|
||||
href?: string
|
||||
}>
|
||||
}
|
||||
|
||||
// Page wrapper layout types
|
||||
export type PageLayout = 'default' | 'bento' | 'centered'
|
||||
|
||||
export interface PageWrapperProps {
|
||||
children: ReactNode
|
||||
header?: PageHeaderProps
|
||||
layout?: PageLayout
|
||||
className?: string
|
||||
}
|
||||
|
||||
// Dropdown component types
|
||||
export interface DropdownOption {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface DropdownProps {
|
||||
label?: string
|
||||
options: DropdownOption[]
|
||||
selectedValue?: string | undefined
|
||||
onSelect: (value: string) => void
|
||||
placeholder?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
// Step header types (for onboarding)
|
||||
export interface StepHeaderProps {
|
||||
title: string
|
||||
description: string
|
||||
icon?: ReactNode // Required icon property
|
||||
}
|
||||
@@ -1,2 +1,6 @@
|
||||
export * from './deployment'
|
||||
export * from './project'
|
||||
export * from './dashboard'
|
||||
export * from './common'
|
||||
export * from './foundation'
|
||||
export * from './onboarding'
|
||||
@@ -0,0 +1,88 @@
|
||||
// Template type for onboarding
|
||||
export interface Template {
|
||||
id: string
|
||||
name: string
|
||||
description: string // Required string
|
||||
repoFullName: string // Required string
|
||||
framework?: string
|
||||
icon?: string // Optional string
|
||||
}
|
||||
|
||||
// Deployer type (compatible with GraphQL client)
|
||||
export interface Deployer {
|
||||
deployerLrn: string
|
||||
deploymentLrn: string
|
||||
name: string
|
||||
baseDomain: string
|
||||
minimumPayment?: string | null
|
||||
}
|
||||
|
||||
// Organization type
|
||||
export interface Organization {
|
||||
id: string
|
||||
slug: string
|
||||
name: string
|
||||
}
|
||||
|
||||
// Environment variable type for onboarding
|
||||
export interface OnboardingEnvVar {
|
||||
key: string
|
||||
value: string
|
||||
environments: string[]
|
||||
}
|
||||
|
||||
// Complete onboarding form data interface
|
||||
export interface OnboardingFormData {
|
||||
// Step 1: Connect (Repository or Template selection)
|
||||
githubRepo?: string
|
||||
template?: Template
|
||||
projectName?: string
|
||||
deploymentMode?: 'repository' | 'template'
|
||||
|
||||
// Step 2: Configure
|
||||
selectedLrn?: string
|
||||
selectedOrg?: string // Add the missing selectedOrg property
|
||||
environmentVariables?: OnboardingEnvVar[] // Use proper array type
|
||||
deploymentType?: 'auction' | 'lrn' // Add missing properties
|
||||
deployerCount?: string
|
||||
maxPrice?: string
|
||||
paymentAddress?: string // Add missing paymentAddress property
|
||||
|
||||
// Step 3: Deploy
|
||||
deploymentId?: string
|
||||
repositoryUrl?: string // Add missing repositoryUrl property
|
||||
deploymentUrl?: string // Add missing deploymentUrl property
|
||||
projectId?: string // Add missing projectId property
|
||||
}
|
||||
|
||||
// Deployment configuration types
|
||||
export interface TemplateDeploymentConfig {
|
||||
template: Template
|
||||
projectName: string
|
||||
organizationSlug: string
|
||||
environmentVariables?: OnboardingEnvVar[]
|
||||
deployerLrn: string
|
||||
}
|
||||
|
||||
export interface DeploymentConfig {
|
||||
projectId: string
|
||||
organizationSlug: string
|
||||
repository: string
|
||||
branch: string
|
||||
name: string
|
||||
environmentVariables?: OnboardingEnvVar[]
|
||||
}
|
||||
|
||||
// Repository type for GitHub integration
|
||||
export interface Repository {
|
||||
id: string
|
||||
name: string
|
||||
full_name: string
|
||||
default_branch: string
|
||||
html_url: string
|
||||
description?: string | null
|
||||
owner: {
|
||||
login: string
|
||||
avatar_url: string
|
||||
}
|
||||
}
|
||||
@@ -18,3 +18,10 @@ export interface Project {
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
export enum ProjectStatus {
|
||||
Building = 'Building',
|
||||
Ready = 'Ready',
|
||||
Error = 'Error',
|
||||
Deleting = 'Deleting'
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// src/utils/typeAdapters.ts
|
||||
import {
|
||||
Deployer as GQLDeployer,
|
||||
DeploymentStatus } from '../../../../services/gql-client/src/types';
|
||||
|
||||
// Define your frontend types that may be missing
|
||||
export type ProjectStatus = 'success' | 'pending' | 'error' | 'building' | 'deleting';
|
||||
|
||||
export interface TemplateDetail {
|
||||
id?: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
description?: string;
|
||||
repoFullName?: string;
|
||||
[key: string]: any; // Allow other properties
|
||||
}
|
||||
|
||||
export interface Template {
|
||||
id?: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
description?: string;
|
||||
repoFullName?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface Repository {
|
||||
id: string;
|
||||
name: string;
|
||||
full_name: string;
|
||||
default_branch: string;
|
||||
html_url: string;
|
||||
description?: string; // Note: undefined, not null
|
||||
owner: {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Deployer {
|
||||
deployerLrn: string;
|
||||
deployerId: string;
|
||||
deployerApiUrl: string;
|
||||
baseDomain: string;
|
||||
minimumPayment?: string; // Note: undefined, not null
|
||||
}
|
||||
|
||||
export interface GitHubRepo {
|
||||
id: string;
|
||||
name: string;
|
||||
full_name: string;
|
||||
default_branch: string;
|
||||
html_url: string;
|
||||
description: string | null;
|
||||
owner: {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GraphQL Deployer to Frontend Deployer
|
||||
*/
|
||||
export const adaptDeployer = (gqlDeployer: GQLDeployer): Deployer => ({
|
||||
...gqlDeployer,
|
||||
minimumPayment: gqlDeployer.minimumPayment ?? undefined
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert array of GraphQL Deployers to Frontend Deployers
|
||||
*/
|
||||
export const adaptDeployers = (gqlDeployers: GQLDeployer[]): Deployer[] => {
|
||||
return gqlDeployers.map(adaptDeployer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert Template to TemplateDetail (ensuring icon is always string)
|
||||
*/
|
||||
export const adaptTemplate = (template: Template): TemplateDetail => ({
|
||||
...template,
|
||||
icon: template.icon || '/default-template-icon.svg' // Provide a default icon path
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert DeploymentStatus to ProjectStatus
|
||||
*/
|
||||
export const adaptDeploymentStatus = (status: string | DeploymentStatus): ProjectStatus => {
|
||||
// Convert to string safely
|
||||
const statusStr = String(status);
|
||||
|
||||
switch (statusStr) {
|
||||
case 'Building':
|
||||
case 'building':
|
||||
return 'building';
|
||||
case 'Ready':
|
||||
case 'ready':
|
||||
case 'complete':
|
||||
return 'success';
|
||||
case 'Error':
|
||||
case 'error':
|
||||
case 'failed':
|
||||
return 'error';
|
||||
case 'Deleting':
|
||||
case 'deleting':
|
||||
return 'deleting';
|
||||
case 'pending':
|
||||
return 'pending';
|
||||
default:
|
||||
console.warn(`Unknown deployment status: ${statusStr}`);
|
||||
return 'pending';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert GitHub API Repository to Frontend Repository
|
||||
*/
|
||||
export const adaptRepository = (githubRepo: GitHubRepo): Repository => ({
|
||||
...githubRepo,
|
||||
description: githubRepo.description ?? undefined
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert array of GitHub repositories
|
||||
*/
|
||||
export const adaptRepositories = (githubRepos: GitHubRepo[]): Repository[] => {
|
||||
return githubRepos.map(adaptRepository);
|
||||
};
|
||||
|
||||
/**
|
||||
* Safely extract owner and repo name from selected repository
|
||||
*/
|
||||
export const extractRepoInfo = (selectedRepo: Repository | null | undefined): { owner: string; repo: string } => {
|
||||
if (!selectedRepo) {
|
||||
throw new Error('No repository selected');
|
||||
}
|
||||
|
||||
const owner = selectedRepo.owner?.login;
|
||||
const repo = selectedRepo.name;
|
||||
|
||||
if (!owner || !repo) {
|
||||
throw new Error('Repository owner and name are required');
|
||||
}
|
||||
|
||||
return { owner, repo };
|
||||
};
|
||||
|
||||
/**
|
||||
* Template deployment configuration adapter
|
||||
*/
|
||||
export interface TemplateDeploymentConfig {
|
||||
template: TemplateDetail;
|
||||
projectName: string;
|
||||
organizationSlug: string;
|
||||
environmentVariables: any[];
|
||||
deployerLrn?: string;
|
||||
}
|
||||
|
||||
export const adaptTemplateDeploymentConfig = (config: {
|
||||
template: Template;
|
||||
projectName: string;
|
||||
organizationSlug: string;
|
||||
environmentVariables: any[];
|
||||
deployerLrn?: string;
|
||||
}): TemplateDeploymentConfig => ({
|
||||
...config,
|
||||
template: adaptTemplate(config.template)
|
||||
});
|
||||
|
||||
/**
|
||||
* Utility to handle optional template conversion
|
||||
*/
|
||||
export const adaptOptionalTemplate = (template: Template | undefined): TemplateDetail | undefined => {
|
||||
return template ? adaptTemplate(template) : undefined;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
REGISTRY_BOND_ID=
|
||||
DEPLOYER_LRN=
|
||||
AUTHORITY=
|
||||
+45
-27
@@ -1,5 +1,9 @@
|
||||
# deployer
|
||||
|
||||
The following steps are for deploying the frontend app to vaasl webapp deployer.
|
||||
|
||||
Run these steps in the `apps/deployer/` directory:
|
||||
|
||||
- Install dependencies
|
||||
|
||||
```bash
|
||||
@@ -7,46 +11,60 @@
|
||||
```
|
||||
|
||||
```bash
|
||||
brew install jq # if you do not have jq installed already
|
||||
# if you do not have jq installed already
|
||||
brew install jq
|
||||
# OR
|
||||
sudo apt-get install jq
|
||||
```
|
||||
|
||||
- Run script to deploy app
|
||||
- Copy and update `.env`
|
||||
|
||||
- To deploy frontend app to `dashboard.staging.apps.snowballtools.com`
|
||||
```
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
```bash
|
||||
./deploy-frontend.staging.sh
|
||||
```
|
||||
Set the required variables:
|
||||
|
||||
- To deploy frontend app to `dashboard.apps.snowballtools.com`
|
||||
```
|
||||
REGISTRY_BOND_ID=230cfedda15e78edc8986dfcb870e1b618f65c56e38d2735476d2a8cb3f25e38
|
||||
DEPLOYER_LRN=lrn://vaasl-provider/deployers/webapp-deployer-api.apps.vaasl.io
|
||||
AUTHORITY=laconic
|
||||
```
|
||||
|
||||
```bash
|
||||
./deploy-frontend.sh
|
||||
```
|
||||
- Set the `userKey` in [config.yml](./config.yml)
|
||||
|
||||
Note: The `userKey` should be the private key of the account owning the bond set in `config.yml` (account address: `laconic13maulvmjxnyx3g855vk0lsv5aptf3rpxskynef`)
|
||||
|
||||
- Set the app env values in [deploy-frontend.sh](./deploy-frontend.sh) (Refer to main [readme](/readme.md#3-configure-environment-variables)):
|
||||
<!-- TODO: Set env values from a file -->
|
||||
|
||||
```sh
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: your_clerk_key
|
||||
CLERK_SECRET_KEY: your_clerk_secret
|
||||
NEXT_PUBLIC_WALLET_IFRAME_URL: https://wallet.laconic.com
|
||||
NEXT_PUBLIC_LACONICD_CHAIN_ID: laconic-mainnet
|
||||
NEXT_PUBLIC_API_URL: https://deploy-backend.laconic.com
|
||||
NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN: your_github_token
|
||||
```
|
||||
|
||||
NOTE: Replace `your_clerk_key`, `your_clerk_secret` and `your_github_token` with actual values
|
||||
|
||||
- Run script to deploy app to `deploy.laconic.com`
|
||||
|
||||
```bash
|
||||
./deploy-frontend.sh
|
||||
```
|
||||
|
||||
- Commit the updated [ApplicationRecord](records/application-record.yml) and [ApplicationDeploymentRequest](records/application-deployment-request.yml) files to the repository
|
||||
|
||||
## Notes
|
||||
|
||||
- Any config env can be updated in [records/application-deployment-request.yml](records/application-deployment-request.yml)
|
||||
|
||||
```yml
|
||||
record:
|
||||
...
|
||||
config:
|
||||
env:
|
||||
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
|
||||
...
|
||||
```
|
||||
|
||||
- On changing `LACONIC_HOSTED_CONFIG_app_github_clientid`, the GitHub client ID and secret need to be changed in backend config too
|
||||
|
||||
## Troubleshoot
|
||||
|
||||
- Check deployment status in [web-app deployer](https://console.laconic.com/deployer).
|
||||
|
||||
- Check records in [registry console app](https://console.laconic.com/#/registry).
|
||||
|
||||
- If deployment fails due to low bond balance
|
||||
|
||||
- Check balances
|
||||
|
||||
```bash
|
||||
@@ -54,11 +72,11 @@
|
||||
pnpm laconic registry account get
|
||||
|
||||
# Bond balance
|
||||
pnpm laconic registry bond get --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32
|
||||
pnpm laconic registry bond get --id 230cfedda15e78edc8986dfcb870e1b618f65c56e38d2735476d2a8cb3f25e38
|
||||
```
|
||||
|
||||
- Command to refill bond
|
||||
|
||||
```bash
|
||||
pnpm laconic registry bond refill --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32 --type alnt --quantity 10000000
|
||||
pnpm laconic registry bond refill --id 230cfedda15e78edc8986dfcb870e1b618f65c56e38d2735476d2a8cb3f25e38 --type alnt --quantity 10000000
|
||||
```
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
services:
|
||||
registry:
|
||||
rpcEndpoint: https://laconicd-sapo.laconic.com
|
||||
gqlEndpoint: https://laconicd-sapo.laconic.com/api
|
||||
rpcEndpoint: https://laconicd-mainnet-1.laconic.com/
|
||||
gqlEndpoint: https://laconicd-mainnet-1.laconic.com/api
|
||||
userKey:
|
||||
bondId:
|
||||
chainId: laconic-testnet-2
|
||||
bondId: 230cfedda15e78edc8986dfcb870e1b618f65c56e38d2735476d2a8cb3f25e38
|
||||
chainId: laconic-mainnet
|
||||
gasPrice: 0.001alnt
|
||||
|
||||
@@ -6,7 +6,7 @@ echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
|
||||
echo "Using AUTHORITY: $AUTHORITY"
|
||||
|
||||
# Repository URL
|
||||
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
REPO_URL="https://git.vdb.to/NasSharaf/laconic-deployer-frontend"
|
||||
|
||||
# Get the latest commit hash for a branch
|
||||
BRANCH_NAME="main"
|
||||
@@ -25,7 +25,7 @@ CONFIG_FILE=config.yml
|
||||
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
|
||||
|
||||
# Get latest version from registry and increment application-record version
|
||||
NEW_APPLICATION_VERSION=$(pnpm --silent laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "deploy-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
NEW_APPLICATION_VERSION=$(pnpm --silent laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "laconic-deployer-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
|
||||
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
|
||||
# Set application-record version if no previous records were found
|
||||
@@ -39,8 +39,8 @@ record:
|
||||
version: $NEW_APPLICATION_VERSION
|
||||
repository_ref: $LATEST_HASH
|
||||
repository: ["$REPO_URL"]
|
||||
app_type: webapp
|
||||
name: deploy-frontend
|
||||
app_type: webapp/next
|
||||
name: laconic-deployer-frontend
|
||||
app_version: $PACKAGE_VERSION
|
||||
EOF
|
||||
|
||||
@@ -60,7 +60,7 @@ echo "ApplicationRecord published"
|
||||
echo $RECORD_ID
|
||||
|
||||
# Set name to record
|
||||
REGISTRY_APP_LRN="lrn://$AUTHORITY/applications/deploy-frontend"
|
||||
REGISTRY_APP_LRN="lrn://$AUTHORITY/applications/laconic-deployer-frontend"
|
||||
|
||||
sleep 2
|
||||
pnpm --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
@@ -122,19 +122,18 @@ cat >./records/application-deployment-request.yml <<EOF
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
name: deploy-frontend@$PACKAGE_VERSION
|
||||
application: lrn://$AUTHORITY/applications/deploy-frontend@$PACKAGE_VERSION
|
||||
name: laconic-deployer-frontend@$PACKAGE_VERSION
|
||||
application: lrn://$AUTHORITY/applications/laconic-deployer-frontend@$PACKAGE_VERSION
|
||||
deployer: $DEPLOYER_LRN
|
||||
dns: deploy.laconic.com
|
||||
dns: deploy-staging.laconic.com
|
||||
config:
|
||||
env:
|
||||
LACONIC_HOSTED_CONFIG_server_url: https://deploy-backend.laconic.com
|
||||
LACONIC_HOSTED_CONFIG_github_clientid: Ov23li4NtYybQlF6u5Dk
|
||||
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
|
||||
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
|
||||
LACONIC_HOSTED_CONFIG_wallet_iframe_url: https://wallet.laconic.com
|
||||
NEXT_PUBLIC_WALLET_IFRAME_URL: https://wallet.laconic.com
|
||||
NEXT_PUBLIC_LACONICD_CHAIN_ID: laconic-mainnet
|
||||
NEXT_PUBLIC_API_URL: https://deploy-backend.laconic.com
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:
|
||||
CLERK_SECRET_KEY:
|
||||
NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN: your_github_token
|
||||
meta:
|
||||
note: Added @ $CURRENT_DATE_TIME
|
||||
repository: "$REPO_URL"
|
||||
@@ -142,6 +141,12 @@ record:
|
||||
payment: $txHash
|
||||
EOF
|
||||
|
||||
# Old env values for frontend app
|
||||
# LACONIC_HOSTED_CONFIG_github_clientid: Ov23li4NtYybQlF6u5Dk
|
||||
# LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
|
||||
# LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
|
||||
# LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
|
||||
|
||||
RECORD_FILE=records/application-deployment-request.yml
|
||||
|
||||
sleep 2
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
PKG_DIR="./apps/deploy-fe"
|
||||
|
||||
cat > $PKG_DIR/.env <<EOF
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=CERC_RUNTIME_ENV_NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
|
||||
CLERK_SECRET_KEY=CERC_RUNTIME_ENV_CLERK_SECRET_KEY
|
||||
NEXT_PUBLIC_WALLET_IFRAME_URL=CERC_RUNTIME_ENV_NEXT_PUBLIC_WALLET_IFRAME_URL
|
||||
NEXT_PUBLIC_LACONICD_CHAIN_ID=CERC_RUNTIME_ENV_NEXT_PUBLIC_LACONICD_CHAIN_ID
|
||||
NEXT_PUBLIC_API_URL=CERC_RUNTIME_ENV_NEXT_PUBLIC_API_URL
|
||||
NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN=CERC_RUNTIME_ENV_NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN
|
||||
EOF
|
||||
|
||||
pnpm install || exit 1
|
||||
|
||||
# TODO: Fix build script to run
|
||||
# pnpm build --filter deploy-fe || exit 1
|
||||
|
||||
pnpm build --filter @workspace/gql-client || exit 1
|
||||
Generated
+30
-20
@@ -127,8 +127,8 @@ importers:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@20.17.23)(typescript@5.8.2)
|
||||
typeorm:
|
||||
specifier: ^0.3.19
|
||||
version: 0.3.21(better-sqlite3@9.6.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2))
|
||||
specifier: ^0.3.26
|
||||
version: 0.3.26(better-sqlite3@12.2.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2))
|
||||
typescript:
|
||||
specifier: ^5.3.3
|
||||
version: 5.8.2
|
||||
@@ -152,8 +152,8 @@ importers:
|
||||
specifier: ^11.0.4
|
||||
version: 11.0.4
|
||||
better-sqlite3:
|
||||
specifier: ^9.2.2
|
||||
version: 9.6.0
|
||||
specifier: ^12.2.0
|
||||
version: 12.2.0
|
||||
copyfiles:
|
||||
specifier: ^2.4.1
|
||||
version: 2.4.1
|
||||
@@ -2892,8 +2892,9 @@ packages:
|
||||
before-after-hook@3.0.2:
|
||||
resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==}
|
||||
|
||||
better-sqlite3@9.6.0:
|
||||
resolution: {integrity: sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==}
|
||||
better-sqlite3@12.2.0:
|
||||
resolution: {integrity: sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ==}
|
||||
engines: {node: 20.x || 22.x || 23.x || 24.x}
|
||||
|
||||
big-integer@1.6.36:
|
||||
resolution: {integrity: sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==}
|
||||
@@ -3323,6 +3324,14 @@ packages:
|
||||
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
dedent@1.6.0:
|
||||
resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==}
|
||||
peerDependencies:
|
||||
babel-plugin-macros: ^3.1.0
|
||||
peerDependenciesMeta:
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
|
||||
deep-extend@0.6.0:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
@@ -5374,29 +5383,28 @@ packages:
|
||||
typeforce@1.18.0:
|
||||
resolution: {integrity: sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==}
|
||||
|
||||
typeorm@0.3.21:
|
||||
resolution: {integrity: sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==}
|
||||
typeorm@0.3.26:
|
||||
resolution: {integrity: sha512-o2RrBNn3lczx1qv4j+JliVMmtkPSqEGpG0UuZkt9tCfWkoXKu8MZnjvp2GjWPll1SehwemQw6xrbVRhmOglj8Q==}
|
||||
engines: {node: '>=16.13.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@google-cloud/spanner': ^5.18.0
|
||||
'@sap/hana-client': ^2.12.25
|
||||
better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
|
||||
hdb-pool: ^0.1.6
|
||||
'@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0
|
||||
'@sap/hana-client': ^2.14.22
|
||||
better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0
|
||||
ioredis: ^5.0.4
|
||||
mongodb: ^5.8.0
|
||||
mongodb: ^5.8.0 || ^6.0.0
|
||||
mssql: ^9.1.1 || ^10.0.1 || ^11.0.1
|
||||
mysql2: ^2.2.5 || ^3.0.1
|
||||
oracledb: ^6.3.0
|
||||
pg: ^8.5.1
|
||||
pg-native: ^3.0.0
|
||||
pg-query-stream: ^4.0.0
|
||||
redis: ^3.1.1 || ^4.0.0
|
||||
redis: ^3.1.1 || ^4.0.0 || ^5.0.14
|
||||
reflect-metadata: ^0.1.14 || ^0.2.0
|
||||
sql.js: ^1.4.0
|
||||
sqlite3: ^5.0.3
|
||||
ts-node: ^10.7.0
|
||||
typeorm-aurora-data-api-driver: ^2.0.0
|
||||
typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0
|
||||
peerDependenciesMeta:
|
||||
'@google-cloud/spanner':
|
||||
optional: true
|
||||
@@ -5404,8 +5412,6 @@ packages:
|
||||
optional: true
|
||||
better-sqlite3:
|
||||
optional: true
|
||||
hdb-pool:
|
||||
optional: true
|
||||
ioredis:
|
||||
optional: true
|
||||
mongodb:
|
||||
@@ -8454,7 +8460,7 @@ snapshots:
|
||||
|
||||
before-after-hook@3.0.2: {}
|
||||
|
||||
better-sqlite3@9.6.0:
|
||||
better-sqlite3@12.2.0:
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
prebuild-install: 7.1.3
|
||||
@@ -8925,6 +8931,8 @@ snapshots:
|
||||
dependencies:
|
||||
mimic-response: 3.1.0
|
||||
|
||||
dedent@1.6.0: {}
|
||||
|
||||
deep-extend@0.6.0: {}
|
||||
|
||||
defaults@1.0.4:
|
||||
@@ -11206,7 +11214,7 @@ snapshots:
|
||||
|
||||
typeforce@1.18.0: {}
|
||||
|
||||
typeorm@0.3.21(better-sqlite3@9.6.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2)):
|
||||
typeorm@0.3.26(better-sqlite3@12.2.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2)):
|
||||
dependencies:
|
||||
'@sqltools/formatter': 1.2.5
|
||||
ansis: 3.17.0
|
||||
@@ -11214,6 +11222,7 @@ snapshots:
|
||||
buffer: 6.0.3
|
||||
dayjs: 1.11.13
|
||||
debug: 4.4.0
|
||||
dedent: 1.6.0
|
||||
dotenv: 16.4.7
|
||||
glob: 10.4.5
|
||||
reflect-metadata: 0.2.2
|
||||
@@ -11223,9 +11232,10 @@ snapshots:
|
||||
uuid: 11.1.0
|
||||
yargs: 17.7.2
|
||||
optionalDependencies:
|
||||
better-sqlite3: 9.6.0
|
||||
better-sqlite3: 12.2.0
|
||||
ts-node: 10.9.2(@types/node@20.17.23)(typescript@5.8.2)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
typescript@5.8.2: {}
|
||||
|
||||
@@ -41,7 +41,7 @@ Create a `.env.local` file in the `apps/deploy-fe` directory:
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_key
|
||||
CLERK_SECRET_KEY=your_clerk_secret
|
||||
NEXT_PUBLIC_WALLET_IFRAME_URL=http://localhost:4000
|
||||
NEXT_PUBLIC_LACONICD_CHAIN_ID=laconic-testnet-2
|
||||
NEXT_PUBLIC_LACONICD_CHAIN_ID=laconic-mainnet
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN=your_github_token
|
||||
```
|
||||
@@ -92,4 +92,26 @@ The frontend is built with:
|
||||
Make sure you have:
|
||||
1. Connected your GitHub account in Clerk
|
||||
2. Provided the necessary permissions for repository access
|
||||
3. Set a fallback GitHub token in `.env.local` if needed for development
|
||||
3. Set a fallback GitHub token in `.env.local` if needed for development
|
||||
|
||||
## Test deployment
|
||||
|
||||
- Follow the dev install steps for stack-orchestrator from <https://git.vdb.to/cerc-io/stack-orchestrator/src/branch/main/docs/CONTRIBUTING.md#install>
|
||||
|
||||
- Build the container for app
|
||||
|
||||
```bash
|
||||
laconic-so build-webapp --source-repo <path-to-app-repo>/laconic-deployer-frontend --base-container cerc/nextjs-base
|
||||
```
|
||||
|
||||
- Create an env file as described in [previous steps](#3-configure-environment-variables)
|
||||
|
||||
- Run the webapp container locally
|
||||
|
||||
```bash
|
||||
docker run -p 3000:80 --env-file <path-to-env-file> cerc/laconic-deployer-frontend:local
|
||||
```
|
||||
|
||||
- Scripts used for deployment
|
||||
- App container build : [build-webapp.sh](./build-webapp.sh)
|
||||
- Run app container : [run-webapp.sh](./run-webapp.sh)
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
./.github/CONTRIBUTING.md
|
||||
./.turbo/cache/88602030b8f39256-meta.json
|
||||
./.turbo/cache/981738a1c69b2e12-meta.json
|
||||
./.turbo/cache/cff97096ef4824bd-meta.json
|
||||
./.turbo/preferences/tui.json
|
||||
./.vscode/extensions.json
|
||||
./.vscode/settings.json
|
||||
./BACKEND_CONNECTION.md
|
||||
./apps/backend/README.md
|
||||
./apps/backend/biome.json
|
||||
./apps/backend/dist/config.d.ts
|
||||
./apps/backend/dist/constants.d.ts
|
||||
./apps/backend/dist/database.d.ts
|
||||
./apps/backend/dist/entity/Deployer.d.ts
|
||||
./apps/backend/dist/entity/Deployment.d.ts
|
||||
./apps/backend/dist/entity/Domain.d.ts
|
||||
./apps/backend/dist/entity/EnvironmentVariable.d.ts
|
||||
./apps/backend/dist/entity/Organization.d.ts
|
||||
./apps/backend/dist/entity/Project.d.ts
|
||||
./apps/backend/dist/entity/ProjectMember.d.ts
|
||||
./apps/backend/dist/entity/User.d.ts
|
||||
./apps/backend/dist/entity/UserOrganization.d.ts
|
||||
./apps/backend/dist/index.d.ts
|
||||
./apps/backend/dist/registry.d.ts
|
||||
./apps/backend/dist/resolvers.d.ts
|
||||
./apps/backend/dist/routes/auth.d.ts
|
||||
./apps/backend/dist/routes/github.d.ts
|
||||
./apps/backend/dist/routes/staging.d.ts
|
||||
./apps/backend/dist/server.d.ts
|
||||
./apps/backend/dist/service.d.ts
|
||||
./apps/backend/dist/turnkey-backend.d.ts
|
||||
./apps/backend/dist/types.d.ts
|
||||
./apps/backend/dist/utils.d.ts
|
||||
./apps/backend/environments/local.toml
|
||||
./apps/backend/package.json
|
||||
./apps/backend/src/config.ts
|
||||
./apps/backend/src/constants.ts
|
||||
./apps/backend/src/database.ts
|
||||
./apps/backend/src/entity/Deployer.ts
|
||||
./apps/backend/src/entity/Deployment.ts
|
||||
./apps/backend/src/entity/Domain.ts
|
||||
./apps/backend/src/entity/EnvironmentVariable.ts
|
||||
./apps/backend/src/entity/Organization.ts
|
||||
./apps/backend/src/entity/Project.ts
|
||||
./apps/backend/src/entity/ProjectMember.ts
|
||||
./apps/backend/src/entity/User.ts
|
||||
./apps/backend/src/entity/UserOrganization.ts
|
||||
./apps/backend/src/index.ts
|
||||
./apps/backend/src/registry.ts
|
||||
./apps/backend/src/resolvers.ts
|
||||
./apps/backend/src/routes/auth.ts
|
||||
./apps/backend/src/routes/github.ts
|
||||
./apps/backend/src/routes/staging.ts
|
||||
./apps/backend/src/server.ts
|
||||
./apps/backend/src/service.ts
|
||||
./apps/backend/src/turnkey-backend.ts
|
||||
./apps/backend/src/types.ts
|
||||
./apps/backend/src/utils.ts
|
||||
./apps/backend/test/delete-db.ts
|
||||
./apps/backend/test/fixtures/deployments.json
|
||||
./apps/backend/test/fixtures/environment-variables.json
|
||||
./apps/backend/test/fixtures/organizations.json
|
||||
./apps/backend/test/fixtures/primary-domains.json
|
||||
./apps/backend/test/fixtures/project-members.json
|
||||
./apps/backend/test/fixtures/projects.json
|
||||
./apps/backend/test/fixtures/redirected-domains.json
|
||||
./apps/backend/test/fixtures/user-organizations.json
|
||||
./apps/backend/test/fixtures/users.json
|
||||
./apps/backend/test/initialize-db.ts
|
||||
./apps/backend/test/initialize-registry.ts
|
||||
./apps/backend/test/publish-deploy-records.ts
|
||||
./apps/backend/test/publish-deployment-removal-records.ts
|
||||
./apps/backend/tsconfig.json
|
||||
./apps/deploy-fe/.vscode/settings.json
|
||||
./apps/deploy-fe/components.json
|
||||
./apps/deploy-fe/next-env.d.ts
|
||||
./apps/deploy-fe/package.json
|
||||
./apps/deploy-fe/src/actions/github.ts
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/documentation/DocumentationPlaceholder.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/documentation/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/home/loading.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/home/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/layout.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/(create)/cr/(template)/tm/(configure)/cf/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/(create)/cr/(template)/tm/(deploy)/dp/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/(create)/cr/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(deployments)/dep/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(integrations)/int/GitPage.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(integrations)/int/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(collaborators)/col/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(domains)/dom/(add)/cf/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(domains)/dom/(add)/config/cf/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(environment-variables)/env/EnvVarsPage.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(environment-variables)/env/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(git)/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/ProjectSettingsPage.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/deployments/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/layout.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/loading.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/error.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/loading.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/purchase/BuyServices.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/purchase/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/store/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/support/SupportPlaceholder.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/support/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/wallet/page.tsx
|
||||
./apps/deploy-fe/src/app/(web3-authenticated)/layout.tsx
|
||||
./apps/deploy-fe/src/app/actions/github.ts
|
||||
./apps/deploy-fe/src/app/api/auth/route.ts
|
||||
./apps/deploy-fe/src/app/api/github/webhook/route.ts
|
||||
./apps/deploy-fe/src/app/auth/github/backend-callback/page.tsx
|
||||
./apps/deploy-fe/src/app/layout.tsx
|
||||
./apps/deploy-fe/src/app/loading.tsx
|
||||
./apps/deploy-fe/src/app/page.tsx
|
||||
./apps/deploy-fe/src/app/sign-in/[[...sign-in]]/page.tsx
|
||||
./apps/deploy-fe/src/app/test-connection/page.tsx
|
||||
./apps/deploy-fe/src/components/AuthTest.tsx
|
||||
./apps/deploy-fe/src/components/DeploymentTest.tsx
|
||||
./apps/deploy-fe/src/components/DevAuth.tsx
|
||||
./apps/deploy-fe/src/components/DirectKeyAuth.tsx
|
||||
./apps/deploy-fe/src/components/GItHubBackendAuth.tsx
|
||||
./apps/deploy-fe/src/components/GQLTest.tsx
|
||||
./apps/deploy-fe/src/components/TurnkeyAuth.tsx
|
||||
./apps/deploy-fe/src/components/WalletConnectAuth.tsx
|
||||
./apps/deploy-fe/src/components/assets/laconic-mark.tsx
|
||||
./apps/deploy-fe/src/components/core/dropdown/Dropdown.tsx
|
||||
./apps/deploy-fe/src/components/core/dropdown/README.md
|
||||
./apps/deploy-fe/src/components/core/dropdown/index.ts
|
||||
./apps/deploy-fe/src/components/core/dropdown/types.ts
|
||||
./apps/deploy-fe/src/components/core/format-milli-second/FormatMilliSecond.tsx
|
||||
./apps/deploy-fe/src/components/core/format-milli-second/README.md
|
||||
./apps/deploy-fe/src/components/core/format-milli-second/index.ts
|
||||
./apps/deploy-fe/src/components/core/format-milli-second/types.ts
|
||||
./apps/deploy-fe/src/components/core/logo/Logo.tsx
|
||||
./apps/deploy-fe/src/components/core/logo/README.md
|
||||
./apps/deploy-fe/src/components/core/logo/index.ts
|
||||
./apps/deploy-fe/src/components/core/logo/types.ts
|
||||
./apps/deploy-fe/src/components/core/search-bar/README.md
|
||||
./apps/deploy-fe/src/components/core/search-bar/SearchBar.tsx
|
||||
./apps/deploy-fe/src/components/core/search-bar/index.ts
|
||||
./apps/deploy-fe/src/components/core/search-bar/types.ts
|
||||
./apps/deploy-fe/src/components/core/stepper/README.md
|
||||
./apps/deploy-fe/src/components/core/stepper/Stepper.tsx
|
||||
./apps/deploy-fe/src/components/core/stepper/index.ts
|
||||
./apps/deploy-fe/src/components/core/stepper/types.ts
|
||||
./apps/deploy-fe/src/components/core/stop-watch/README.md
|
||||
./apps/deploy-fe/src/components/core/stop-watch/StopWatch.tsx
|
||||
./apps/deploy-fe/src/components/core/stop-watch/index.ts
|
||||
./apps/deploy-fe/src/components/core/stop-watch/types.ts
|
||||
./apps/deploy-fe/src/components/core/vertical-stepper/README.md
|
||||
./apps/deploy-fe/src/components/core/vertical-stepper/VerticalStepper.tsx
|
||||
./apps/deploy-fe/src/components/core/vertical-stepper/index.ts
|
||||
./apps/deploy-fe/src/components/core/vertical-stepper/types.ts
|
||||
./apps/deploy-fe/src/components/foundation/coming-soon-overlay/ComingSoonOverlay.tsx
|
||||
./apps/deploy-fe/src/components/foundation/coming-soon-overlay/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/github-session-button/GitHubSessionButton.tsx
|
||||
./apps/deploy-fe/src/components/foundation/github-session-button/README.md
|
||||
./apps/deploy-fe/src/components/foundation/github-session-button/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/github-session-button/types.ts
|
||||
./apps/deploy-fe/src/components/foundation/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/laconic-icon/LaconicIcon.tsx
|
||||
./apps/deploy-fe/src/components/foundation/laconic-icon/README.md
|
||||
./apps/deploy-fe/src/components/foundation/laconic-icon/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/laconic-icon/types.ts
|
||||
./apps/deploy-fe/src/components/foundation/loading/loading-overlay/LoadingOverlay.tsx
|
||||
./apps/deploy-fe/src/components/foundation/loading/loading-overlay/README.md
|
||||
./apps/deploy-fe/src/components/foundation/loading/loading-overlay/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/navigation-wrapper/NavigationWrapper.tsx
|
||||
./apps/deploy-fe/src/components/foundation/navigation-wrapper/README.md
|
||||
./apps/deploy-fe/src/components/foundation/navigation-wrapper/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/page-header/PageHeader.tsx
|
||||
./apps/deploy-fe/src/components/foundation/page-header/README.md
|
||||
./apps/deploy-fe/src/components/foundation/page-header/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/page-wrapper/PageWrapper.tsx
|
||||
./apps/deploy-fe/src/components/foundation/page-wrapper/README.md
|
||||
./apps/deploy-fe/src/components/foundation/page-wrapper/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/project-search-bar/ProjectSearchBar.tsx
|
||||
./apps/deploy-fe/src/components/foundation/project-search-bar/README.md
|
||||
./apps/deploy-fe/src/components/foundation/project-search-bar/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/project-search-bar/types.ts
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/README.md
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/dark-mode-toggle/DarkModeToggle.tsx
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/dark-mode-toggle/README.md
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/dark-mode-toggle/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/main-navigation/MainNavigation.tsx
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/main-navigation/README.md
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/main-navigation/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/navigation-item/NavigationItem.tsx
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/navigation-item/README.md
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/navigation-item/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/types.ts
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/wallet-session-badge/README.md
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/wallet-session-badge/WalletSessionBadge.tsx
|
||||
./apps/deploy-fe/src/components/foundation/top-navigation/wallet-session-badge/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/types.ts
|
||||
./apps/deploy-fe/src/components/foundation/wallet-session-id/README.md
|
||||
./apps/deploy-fe/src/components/foundation/wallet-session-id/WalletSessionId.tsx
|
||||
./apps/deploy-fe/src/components/foundation/wallet-session-id/index.ts
|
||||
./apps/deploy-fe/src/components/foundation/wallet-session-id/types.ts
|
||||
./apps/deploy-fe/src/components/iframe/auto-sign-in/AutoSignInIFrameModal.tsx
|
||||
./apps/deploy-fe/src/components/iframe/auto-sign-in/README.md
|
||||
./apps/deploy-fe/src/components/iframe/auto-sign-in/index.ts
|
||||
./apps/deploy-fe/src/components/iframe/auto-sign-in/types.ts
|
||||
./apps/deploy-fe/src/components/iframe/check-balance-iframe/CheckBalanceIframe.tsx
|
||||
./apps/deploy-fe/src/components/iframe/check-balance-iframe/CheckBalanceWrapper.tsx
|
||||
./apps/deploy-fe/src/components/iframe/check-balance-iframe/useCheckBalance.tsx
|
||||
./apps/deploy-fe/src/components/layout/index.ts
|
||||
./apps/deploy-fe/src/components/layout/navigation/github-session-button/GitHubSessionButton.tsx
|
||||
./apps/deploy-fe/src/components/layout/navigation/github-session-button/README.md
|
||||
./apps/deploy-fe/src/components/layout/navigation/github-session-button/index.ts
|
||||
./apps/deploy-fe/src/components/layout/navigation/github-session-button/types.ts
|
||||
./apps/deploy-fe/src/components/layout/navigation/laconic-icon/LaconicIcon.tsx
|
||||
./apps/deploy-fe/src/components/layout/navigation/laconic-icon/README.md
|
||||
./apps/deploy-fe/src/components/layout/navigation/laconic-icon/index.ts
|
||||
./apps/deploy-fe/src/components/layout/navigation/laconic-icon/types.ts
|
||||
./apps/deploy-fe/src/components/layout/navigation/navigation-actions/NavigationActions.tsx
|
||||
./apps/deploy-fe/src/components/layout/navigation/navigation-actions/README.md
|
||||
./apps/deploy-fe/src/components/layout/navigation/navigation-actions/index.ts
|
||||
./apps/deploy-fe/src/components/layout/navigation/navigation-actions/types.ts
|
||||
./apps/deploy-fe/src/components/layout/navigation/wallet-session-id/README.md
|
||||
./apps/deploy-fe/src/components/layout/navigation/wallet-session-id/WalletSessionId.tsx
|
||||
./apps/deploy-fe/src/components/layout/navigation/wallet-session-id/index.ts
|
||||
./apps/deploy-fe/src/components/layout/navigation/wallet-session-id/types.ts
|
||||
./apps/deploy-fe/src/components/loading/loading-overlay.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/OPTIMIZATION.md
|
||||
./apps/deploy-fe/src/components/onboarding/Onboarding.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/OnboardingButton.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/OnboardingDialog.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/OnboardingSidebar.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/README.md
|
||||
./apps/deploy-fe/src/components/onboarding/common/background-svg.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/common/index.ts
|
||||
./apps/deploy-fe/src/components/onboarding/common/laconic-icon-lettering.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/common/onboarding-container.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/common/step-header.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/common/step-navigation.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/configure-step/configure-step.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/configure-step/disable_configure-step.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/configure-step/index.ts
|
||||
./apps/deploy-fe/src/components/onboarding/connect-step/connect-button.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/connect-step/connect-deploy-first-app.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/connect-step/connect-initial.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/connect-step/connect-step.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/connect-step/disabled_connect-step.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/connect-step/index.ts
|
||||
./apps/deploy-fe/src/components/onboarding/connect-step/repository-list.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/connect-step/template-list.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/deploy-step/deploy-step.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/deploy-step/disabled_deploy-step.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/deploy-step/index.ts
|
||||
./apps/deploy-fe/src/components/onboarding/index.ts
|
||||
./apps/deploy-fe/src/components/onboarding/sidebar/index.ts
|
||||
./apps/deploy-fe/src/components/onboarding/sidebar/sidebar-nav.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/store.ts
|
||||
./apps/deploy-fe/src/components/onboarding/success-step/success-step.tsx
|
||||
./apps/deploy-fe/src/components/onboarding/types.ts
|
||||
./apps/deploy-fe/src/components/onboarding/useOnboarding.ts
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectCard/FixedProjectCard.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectCard/ProjectCard.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectCard/ProjectCardActions.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectCard/ProjectDeploymentInfo.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectCard/ProjectStatusDot.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectCard/index.ts
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectSearchBar/ProjectSearchBar.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectSearchBar/ProjectSearchBarDialog.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectSearchBar/ProjectSearchBarEmpty.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectSearchBar/ProjectSearchBarItem.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/ProjectSearchBar/index.ts
|
||||
./apps/deploy-fe/src/components/projects/project/deployments/DeploymentDetailsCard.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/deployments/FilterForm.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/overview/Activity/AuctionCard.tsx
|
||||
./apps/deploy-fe/src/components/projects/project/overview/OverviewInfo.tsx
|
||||
./apps/deploy-fe/src/components/providers.tsx
|
||||
./apps/deploy-fe/src/context/GQLClientContext.tsx
|
||||
./apps/deploy-fe/src/context/OctokitContext.tsx
|
||||
./apps/deploy-fe/src/context/OctokitProviderWithRouter.tsx
|
||||
./apps/deploy-fe/src/context/WalletContext.tsx
|
||||
./apps/deploy-fe/src/context/WalletContextProvider.tsx
|
||||
./apps/deploy-fe/src/context/index.ts
|
||||
./apps/deploy-fe/src/hooks/disabled_useDeployment.tsx
|
||||
./apps/deploy-fe/src/hooks/disabled_useRepoData.tsx
|
||||
./apps/deploy-fe/src/hooks/useDeployment.tsx
|
||||
./apps/deploy-fe/src/hooks/useRepoData.tsx
|
||||
./apps/deploy-fe/src/hooks/useRepoSelection.tsx
|
||||
./apps/deploy-fe/src/lib/utils.ts
|
||||
./apps/deploy-fe/src/middleware.ts
|
||||
./apps/deploy-fe/src/types/common.ts
|
||||
./apps/deploy-fe/src/types/dashboard.ts
|
||||
./apps/deploy-fe/src/types/deployment.ts
|
||||
./apps/deploy-fe/src/types/hooks/use-mobile.tsx
|
||||
./apps/deploy-fe/src/types/index.ts
|
||||
./apps/deploy-fe/src/types/project.ts
|
||||
./apps/deploy-fe/src/utils/getInitials.ts
|
||||
./apps/deploy-fe/src/utils/time.ts
|
||||
./apps/deploy-fe/standards/architecture/routes.md
|
||||
./apps/deploy-fe/tailwind.config.ts
|
||||
./apps/deploy-fe/tsconfig.json
|
||||
./apps/deployer/README.md
|
||||
./apps/deployer/biome.json
|
||||
./apps/deployer/package.json
|
||||
./apps/deployer/test/README.md
|
||||
./biome.json
|
||||
./docs/architecture/wallet_migration/0-wallet-integration-overview.md
|
||||
./docs/architecture/wallet_migration/1-phase-1-wallet-core.md
|
||||
./docs/architecture/wallet_migration/2-phase-2-wallet-ui.md
|
||||
./docs/architecture/wallet_migration/3-phase-3-clerk-integration.md
|
||||
./next-agent-01.md
|
||||
./package.json
|
||||
./readme.md
|
||||
./scripts/README.md
|
||||
./services/gql-client/biome.json
|
||||
./services/gql-client/dist/index.d.ts
|
||||
./services/gql-client/package.json
|
||||
./services/gql-client/src/client.ts
|
||||
./services/gql-client/src/index.ts
|
||||
./services/gql-client/src/mutations.ts
|
||||
./services/gql-client/src/queries.ts
|
||||
./services/gql-client/src/types.ts
|
||||
./services/gql-client/tsconfig.json
|
||||
./services/gql-client/tsup.config.ts
|
||||
./services/typescript-config/README.md
|
||||
./services/typescript-config/base.json
|
||||
./services/typescript-config/nextjs.json
|
||||
./services/typescript-config/package.json
|
||||
./services/typescript-config/react-library.json
|
||||
./services/ui/components.json
|
||||
./services/ui/package.json
|
||||
./services/ui/src/components/accordion.tsx
|
||||
./services/ui/src/components/alert-dialog.tsx
|
||||
./services/ui/src/components/alert.tsx
|
||||
./services/ui/src/components/aspect-ratio.tsx
|
||||
./services/ui/src/components/avatar.tsx
|
||||
./services/ui/src/components/badge.tsx
|
||||
./services/ui/src/components/breadcrumb.tsx
|
||||
./services/ui/src/components/button.tsx
|
||||
./services/ui/src/components/calendar.tsx
|
||||
./services/ui/src/components/card.tsx
|
||||
./services/ui/src/components/carousel.tsx
|
||||
./services/ui/src/components/chart.tsx
|
||||
./services/ui/src/components/checkbox.tsx
|
||||
./services/ui/src/components/collapsible.tsx
|
||||
./services/ui/src/components/command.tsx
|
||||
./services/ui/src/components/context-menu.tsx
|
||||
./services/ui/src/components/dialog.tsx
|
||||
./services/ui/src/components/drawer.tsx
|
||||
./services/ui/src/components/dropdown-menu.tsx
|
||||
./services/ui/src/components/form.tsx
|
||||
./services/ui/src/components/hover-card.tsx
|
||||
./services/ui/src/components/input-otp.tsx
|
||||
./services/ui/src/components/input.tsx
|
||||
./services/ui/src/components/label.tsx
|
||||
./services/ui/src/components/menubar.tsx
|
||||
./services/ui/src/components/navigation-menu.tsx
|
||||
./services/ui/src/components/pagination.tsx
|
||||
./services/ui/src/components/popover.tsx
|
||||
./services/ui/src/components/progress.tsx
|
||||
./services/ui/src/components/radio-group.tsx
|
||||
./services/ui/src/components/resizable.tsx
|
||||
./services/ui/src/components/scroll-area.tsx
|
||||
./services/ui/src/components/select.tsx
|
||||
./services/ui/src/components/separator.tsx
|
||||
./services/ui/src/components/sheet.tsx
|
||||
./services/ui/src/components/sidebar.tsx
|
||||
./services/ui/src/components/skeleton.tsx
|
||||
./services/ui/src/components/slider.tsx
|
||||
./services/ui/src/components/sonner.tsx
|
||||
./services/ui/src/components/switch.tsx
|
||||
./services/ui/src/components/table.tsx
|
||||
./services/ui/src/components/tabs.tsx
|
||||
./services/ui/src/components/textarea.tsx
|
||||
./services/ui/src/components/toggle-group.tsx
|
||||
./services/ui/src/components/toggle.tsx
|
||||
./services/ui/src/components/tooltip.tsx
|
||||
./services/ui/src/hooks/use-mobile.ts
|
||||
./services/ui/src/lib/utils.ts
|
||||
./services/ui/tailwind.config.ts
|
||||
./services/ui/tsconfig.json
|
||||
./services/ui/tsconfig.lint.json
|
||||
./standards/blueprints/file-migration-list.md
|
||||
./standards/blueprints/next-app-router-structure.md
|
||||
./standards/blueprints/nextjs-templates.md
|
||||
./standards/blueprints/qwrk-laconic-migration-guide.md
|
||||
./standards/current-tech-reference.md
|
||||
./standards/documentation/COMPONENT_DOCUMENTATION.md
|
||||
./standards/documentation/FEATURE_BUILDING.md
|
||||
./standards/documentation/FEATURE_BUILDING_TEMPLATE.md
|
||||
./standards/documentation/README.md
|
||||
./standards/documentation/react-component-conventions.md
|
||||
./tsconfig.base.json
|
||||
./tsconfig.json
|
||||
./turbo.json
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
PKG_DIR="./apps/deploy-fe"
|
||||
|
||||
cd $PKG_DIR
|
||||
|
||||
cat > .env.local <<EOF
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
|
||||
CLERK_SECRET_KEY=${CLERK_SECRET_KEY}
|
||||
NEXT_PUBLIC_WALLET_IFRAME_URL=${NEXT_PUBLIC_WALLET_IFRAME_URL}
|
||||
NEXT_PUBLIC_LACONICD_CHAIN_ID=${NEXT_PUBLIC_LACONICD_CHAIN_ID}
|
||||
NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN=${NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN}
|
||||
EOF
|
||||
|
||||
# For debugging
|
||||
# cat .env.local
|
||||
|
||||
# TODO: Need production build in .next. Check build-webapp.sh
|
||||
# pnpm start -p ${CERC_LISTEN_PORT:-80}
|
||||
|
||||
pnpm dev -p ${CERC_LISTEN_PORT:-80}
|
||||
Reference in New Issue
Block a user