Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions app/(dashboard)/projects/[id]/_components/settings-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Settings page layout component
* Used for project settings pages (database, environments, etc.)
* VSCode Dark Modern style with clean design
*/

'use client';

import type { ReactNode } from 'react';

import { Skeleton } from '@/components/ui/skeleton';

interface SettingsLayoutProps {
title: string;
description: string;
children: ReactNode;
loading?: boolean;
}

/**
* Layout wrapper for project settings pages
* Uses skeleton to maintain layout stability during loading
*/
export function SettingsLayout({ title, description, children, loading }: SettingsLayoutProps) {
return (
<div className="flex-1 px-8 py-10 pb-20 overflow-y-auto">
<div className="max-w-7xl mx-auto w-full animate-fade-in-up">
<header className="mb-8">
<h1 className="text-3xl font-bold text-foreground mb-2">{title}</h1>
<p className="text-muted-foreground text-sm">{description}</p>
</header>

<div className="space-y-8">
{loading ? (
<>
<Skeleton className="h-48 w-full" />
<Skeleton className="h-48 w-full" />
<Skeleton className="h-48 w-full" />
</>
) : (
children
)}
</div>
</div>
</div>
);
}
34 changes: 17 additions & 17 deletions app/(dashboard)/projects/[id]/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* Authentication Configuration Page
* Configure OAuth providers and NextAuth settings
* VSCode Dark Modern style
*/

'use client';
Expand All @@ -10,14 +9,15 @@ import { ExternalLink, Key } from 'lucide-react';
import { Github } from 'lucide-react';
import { useParams } from 'next/navigation';

import { ConfigLayout } from '@/components/config/config-layout';
import { EnvVarSection } from '@/components/config/env-var-section';
import {
useBatchUpdateEnvironmentVariables,
useEnvironmentVariables,
} from '@/hooks/use-environment-variables';
import { useProject } from '@/hooks/use-project';

import { SettingsLayout } from '../_components/settings-layout';

/**
* Generate a secure random secret
*/
Expand Down Expand Up @@ -87,24 +87,24 @@ function AuthPageContent() {
];

return (
<ConfigLayout
<SettingsLayout
title="Auth Configuration"
description="Configure auth configuration settings for this project."
loading={envLoading || projectLoading}
>
<div>
<div className="space-y-6">
{/* GitHub OAuth Section */}
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<Github className="h-5 w-5 text-[#3794ff]" />
<h2 className="text-base font-medium text-[#cccccc]">GitHub OAuth</h2>
<Github className="h-5 w-5 text-primary" />
<h2 className="text-base font-medium text-foreground">GitHub OAuth</h2>
</div>
<a
href="https://github.com/settings/developers"
target="_blank"
rel="noopener noreferrer"
className="text-xs text-[#3794ff] hover:text-[#4fc1ff] flex items-center gap-1.5 transition-colors"
className="text-xs text-primary hover:text-primary/80 flex items-center gap-1.5 transition-colors"
>
GitHub Developer Settings
<ExternalLink className="h-3.5 w-3.5" />
Expand All @@ -121,9 +121,9 @@ function AuthPageContent() {
/>

{/* Setup Instructions */}
<div className="mt-4 p-4 bg-[#252526] border border-[#3e3e42] rounded">
<h3 className="text-xs font-medium text-[#cccccc] mb-2">Setup Instructions</h3>
<ol className="text-xs text-[#858585] space-y-1 list-decimal list-inside">
<div className="mt-4 p-4 bg-card border border-border rounded">
<h3 className="text-xs font-medium text-foreground mb-2">Setup Instructions</h3>
<ol className="text-xs text-muted-foreground space-y-1 list-decimal list-inside">
<li>Go to GitHub Settings → Developer settings → OAuth Apps</li>
<li>Click &quot;New OAuth App&quot; or select an existing app</li>
<li>Set the Homepage URL and Authorization callback URL</li>
Expand All @@ -134,13 +134,13 @@ function AuthPageContent() {
</div>

{/* Divider */}
<div className="border-t border-[#3e3e42]" />
<div className="border-t border-border" />

{/* NextAuth Configuration Section */}
<div>
<div className="flex items-center gap-2 mb-4">
<Key className="h-5 w-5 text-[#3794ff]" />
<h2 className="text-base font-medium text-[#cccccc]">NextAuth Configuration</h2>
<Key className="h-5 w-5 text-primary" />
<h2 className="text-base font-medium text-foreground">NextAuth Configuration</h2>
</div>

<EnvVarSection
Expand All @@ -153,9 +153,9 @@ function AuthPageContent() {
/>

{/* Important Notes */}
<div className="mt-4 p-4 bg-[#252526] border border-[#3e3e42] rounded">
<h3 className="text-xs font-medium text-[#cccccc] mb-2">Important Notes</h3>
<ul className="text-xs text-[#858585] space-y-1 list-disc list-inside">
<div className="mt-4 p-4 bg-card border border-border rounded">
<h3 className="text-xs font-medium text-foreground mb-2">Important Notes</h3>
<ul className="text-xs text-muted-foreground space-y-1 list-disc list-inside">
<li>The NextAuth URL must match your application URL exactly</li>
<li>The secret should be at least 32 characters long</li>
<li>Never commit your NEXTAUTH_SECRET to version control</li>
Expand All @@ -164,7 +164,7 @@ function AuthPageContent() {
</div>
</div>
</div>
</ConfigLayout>
</SettingsLayout>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use client';

import { useMemo, useState } from 'react';
import {
MdCheck,
MdContentCopy,
MdInfo,
MdVisibility,
MdVisibilityOff,
} from 'react-icons/md';

import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';

interface ConnectionStringProps {
connectionString: string;
}

export function ConnectionString({ connectionString }: ConnectionStringProps) {
const [isVisible, setIsVisible] = useState(false);
const [copied, setCopied] = useState(false);

// Memoize masked string to avoid recalculation on every render
const displayValue = useMemo(() => {
if (isVisible) return connectionString;
return '•'.repeat(Math.min(connectionString.length, 50));
}, [connectionString, isVisible]);

const handleCopy = async () => {
await navigator.clipboard.writeText(connectionString);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

return (
<div className="bg-muted/30 border border-border rounded-lg p-6 shadow-sm">
{/* Header with title and badge */}
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium text-foreground font-sans">
Full Connection String
</h3>
<span className="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded-full font-medium">
Read-only
</span>
</div>

{/* Connection string display */}
<div className="relative group">
<div
className={cn(
'w-full bg-background/50 border border-border rounded-md px-4 py-4',
'text-sm font-mono text-muted-foreground break-all leading-relaxed shadow-inner',
'min-h-[3.5rem] flex items-center'
)}
>
{displayValue}
</div>

{/* Action buttons */}
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
{/* Toggle visibility */}
<Button
onClick={() => setIsVisible(!isVisible)}
variant="secondary"
size="icon"
className="h-8 w-8"
aria-label={isVisible ? 'Hide connection string' : 'Show connection string'}
aria-pressed={isVisible}
type="button"
>
{isVisible ? <MdVisibilityOff className="h-4 w-4" /> : <MdVisibility className="h-4 w-4" />}
</Button>

{/* Copy button */}
<Button
onClick={handleCopy}
variant="secondary"
size="icon"
className="h-8 w-8"
aria-label={copied ? 'Copied' : 'Copy connection string'}
type="button"
>
{copied ? <MdCheck className="h-4 w-4" /> : <MdContentCopy className="h-4 w-4" />}
</Button>
</div>
</div>

{/* Footer with info */}
<p className="mt-3 text-xs text-muted-foreground flex items-center gap-1.5">
<MdInfo className="h-3.5 w-3.5" />
Use this connection string in your application to connect to the database.
</p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { type IconType } from 'react-icons';
import { MdAutoAwesome, MdHttps, MdStorage, MdTerminal } from 'react-icons/md';

// Feature card component
function FeatureCard({
icon: Icon,
title,
description,
}: {
icon: IconType;
title: string;
description: string;
}) {
return (
<div className="bg-card border border-border rounded-lg p-5 hover:border-primary/50 transition-colors">
<div className="mb-3 text-primary">
<Icon className="h-5 w-5" />
</div>
<h4 className="text-sm font-medium text-card-foreground mb-2">{title}</h4>
<p className="text-xs text-muted-foreground leading-relaxed">{description}</p>
</div>
);
}

// Feature cards data
const DATABASE_FEATURES = [
{
icon: MdAutoAwesome,
title: 'Auto Provisioned',
description: 'Database is automatically provisioned and ready to use with your sandbox environment.',
},
{
icon: MdStorage,
title: 'High Availability',
description: 'Managed by KubeBlocks with high availability and automatic failover.',
},
{
icon: MdHttps,
title: 'SSL Encrypted',
description: 'SSL encryption enabled by default for secure database connections.',
},
{
icon: MdTerminal,
title: 'Environment Variable',
description: 'Connection string available via DATABASE_URL environment variable.',
},
] as const;

// Export complete component
export function FeatureCards() {
return (
<div className="grid grid-cols-4 gap-6 pt-4 border-t border-border">
{DATABASE_FEATURES.map(({ icon, title, description }) => (
<FeatureCard key={title} icon={icon} title={title} description={description} />
))}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Read-only field component for displaying data
* Mimics input styling for display-only scenarios
*/

import { useId } from 'react';

import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';

interface ReadOnlyFieldProps {
/** Field label */
label: string;
/** Field value */
value: string | number;
/** Full width on mobile (col-span-2) */
fullWidth?: boolean;
}

export function ReadOnlyField({ label, value, fullWidth }: ReadOnlyFieldProps) {
const fieldId = useId();

return (
<div className={fullWidth ? 'col-span-2' : ''}>
<Label
htmlFor={fieldId}
className="block text-xs text-muted-foreground mb-2 uppercase"
>
{label}
</Label>
<Input
id={fieldId}
readOnly
value={value}
className="bg-muted font-mono text-sm cursor-text focus-visible:ring-0"
/>
</div>
);
}
39 changes: 0 additions & 39 deletions app/(dashboard)/projects/[id]/database/connection-string.tsx

This file was deleted.

Loading