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
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,19 @@ STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRO_PRICE_ID=
DROPS_TEAM_INVITE_SECRET=

# Managed Platform V4. These values select external adapters; setting a name never
# marks the adapter healthy by itself. Production UI waits for provider evidence.
# DROPS_MANAGED_DATA_PROVIDER accepts only "d1" or "postgres" in adapter wiring.
DROPS_MANAGED_DATA_PROVIDER=
DATABASE_URL=
DROPS_COLLABORATION_TRANSPORT_URL=

# Generic enterprise OIDC. Values stay server-only and external login remains
# disabled until discovery, callback, nonce, PKCE, domain and group checks pass.
DROPS_ENTERPRISE_OIDC_ISSUER=
DROPS_ENTERPRISE_OIDC_CLIENT_ID=
DROPS_ENTERPRISE_OIDC_CLIENT_SECRET=

# Separate 32+ byte material for a future durable append-only audit adapter.
DROPS_ENTERPRISE_AUDIT_SIGNING_KEY=
16 changes: 16 additions & 0 deletions app/api/platform/capabilities/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NextResponse } from "next/server.js";

import { platformCapabilitySnapshot } from "@/lib/platform-capabilities";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

export async function GET() {
return NextResponse.json(platformCapabilitySnapshot(), {
status: 200,
headers: {
"cache-control": "private, no-store, max-age=0",
vary: "Cookie",
},
});
}
9 changes: 9 additions & 0 deletions app/backend/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Database, ShieldCheck } from "lucide-react";

import { PlatformCapabilityConsole } from "@/components/platform/platform-capability-console";
import { PlatformShell } from "@/components/platform/platform-shell";
import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";

export default function BackendPage() {
return <PlatformShell active="Backend"><PageIntro eyebrow="Managed backend" title="A complete backend surface, with honest runtime evidence." description="Inspect data, schema, auth, storage, functions, jobs, cron, webhooks, realtime, secrets, logs and recovery. Local reference capabilities and durable production providers never share the same status label." receipt={<><div className="flex items-center gap-3"><span className="grid size-11 place-items-center rounded-xl bg-[#eef4ff] text-[#245fe5]"><Database className="size-5" aria-hidden="true" /></span><div><strong className="text-sm">Data-plane readiness</strong><p className="mt-1 text-xs text-[#52617a]">Live server capability receipt</p></div></div><div className="mt-5 flex items-center justify-between gap-3"><StatusBadge status="local">Reference core verified</StatusBadge><ShieldCheck className="size-5 text-[#139a62]" aria-hidden="true" /></div></>} /><PlatformCapabilityConsole mode="backend" /></PlatformShell>;
}
9 changes: 9 additions & 0 deletions app/enterprise/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ShieldCheck, UsersRound } from "lucide-react";

import { PlatformCapabilityConsole } from "@/components/platform/platform-capability-console";
import { PlatformShell } from "@/components/platform/platform-shell";
import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";

export default function EnterprisePage() {
return <PlatformShell active="Enterprise"><PageIntro eyebrow="Enterprise controls" title="Identity, collaboration and governance without theatre." description="Review the organization, RBAC, collaboration, OIDC, service account, policy, audit and lifecycle contracts. External identity, realtime and durable storage remain setup-required until their health evidence exists." receipt={<><div className="flex items-center gap-3"><span className="grid size-11 place-items-center rounded-xl bg-[#eef4ff] text-[#245fe5]"><UsersRound className="size-5" aria-hidden="true" /></span><div><strong className="text-sm">Control-plane evidence</strong><p className="mt-1 text-xs text-[#52617a]">Tenant-safe reference runtime</p></div></div><div className="mt-5 flex items-center justify-between gap-3"><StatusBadge status="local">Core verified locally</StatusBadge><ShieldCheck className="size-5 text-[#139a62]" aria-hidden="true" /></div></>} /><PlatformCapabilityConsole mode="enterprise" /></PlatformShell>;
}
9 changes: 9 additions & 0 deletions app/integrations/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { KeyRound, ShieldCheck } from "lucide-react";

import { IntegrationCatalog } from "@/components/platform/integration-catalog";
import { PlatformShell } from "@/components/platform/platform-shell";
import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";

export default function IntegrationsPage() {
return <PlatformShell active="Integrations"><PageIntro eyebrow="Connection center" title="Bring intelligence, models, and delivery—safely." description="Manage session-only AI and DropsTab connections, then complete project-specific Drops Bot, Telegram, GitHub, and Vercel setup only when you need them." receipt={<><div className="flex items-center gap-3"><span className="grid size-11 place-items-center rounded-xl bg-[#eef4ff] text-[#245fe5]"><KeyRound className="size-5" aria-hidden="true" /></span><div><strong className="text-sm">No credential values rendered</strong><p className="mt-1 text-xs text-[#52617a]">Only current-tab markers are inspected</p></div></div><div className="mt-5 flex items-center justify-between gap-3"><StatusBadge status="setup">Evidence required</StatusBadge><ShieldCheck className="size-5 text-[#245fe5]" aria-hidden="true" /></div></>} /><IntegrationCatalog /></PlatformShell>;
}
8 changes: 4 additions & 4 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ const productionUrl = /^https?:\/\//i.test(configuredProductionUrl)

export const metadata: Metadata = {
metadataBase: new URL(productionUrl),
title: "Drops Studio — Build crypto products in minutes",
title: "Drops Studio — Build crypto apps 10x faster with AI",
description:
"Build useful crypto apps with DropsTab intelligence, guided Drops Bot setup and the AI model you choose.",
"Plan, build, test and publish editable crypto applications with DropsTab intelligence, Drops Bot automation and the AI model you choose.",
openGraph: {
title: "Drops Studio",
description: "Turn a crypto idea into a live project in five minutes.",
description: "Build editable crypto applications with AI, DropsTab intelligence and Drops Bot automation.",
type: "website",
images: [
{
Expand All @@ -45,7 +45,7 @@ export const metadata: Metadata = {
twitter: {
card: "summary_large_image",
title: "Drops Studio",
description: "Turn a crypto idea into a live project in five minutes.",
description: "Build editable crypto applications with AI, DropsTab intelligence and Drops Bot automation.",
images: ["/og.png"],
},
};
Expand Down
9 changes: 9 additions & 0 deletions app/organizations/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Building2, ShieldCheck } from "lucide-react";

import { OrganizationConsole } from "@/components/platform/organization-console";
import { PlatformShell } from "@/components/platform/platform-shell";
import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";

export default function OrganizationsPage() {
return <PlatformShell active="Organizations"><PageIntro eyebrow="Organizations and workspaces" title="Collaborate without inventing team state." description="Use the current signed team APIs for real workspaces and revisioned shared projects. Rich enterprise roles, OIDC, presence, and policy controls activate only with matching authorization and adapter evidence." receipt={<><div className="flex items-center gap-3"><span className="grid size-11 place-items-center rounded-xl bg-[#eef4ff] text-[#245fe5]"><Building2 className="size-5" aria-hidden="true" /></span><div><strong className="text-sm">Tenant-aware control plane</strong><p className="mt-1 text-xs text-[#52617a]">Account and storage status are read live</p></div></div><div className="mt-5 flex items-center justify-between gap-3"><StatusBadge status="setup">Account dependent</StatusBadge><ShieldCheck className="size-5 text-[#139a62]" aria-hidden="true" /></div></>} /><OrganizationConsole /></PlatformShell>;
}
15 changes: 8 additions & 7 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ function LandingHero() {
return (
<div className="hero-copy">
<span className="eyebrow">
<Sparkles size={14} /> BUILD IN 5 MINUTES
<Sparkles size={14} /> DROPS AI CRYPTO BUILDER
</span>
<h1>
Turn a crypto idea{" "}
Build crypto apps{" "}
<br className="hero-break" />
into a live project<span>.</span>
10x faster with AI<span>.</span>
</h1>
<div className="hero-description">
<p>
Start from 12 extensible working foundations or describe any crypto
product.
Describe a product or start from 12 extensible working foundations
for category-native crypto apps. Review the plan, then build an editable
multi-file application.
</p>
<p>
Drops Studio assembles the data, triggers, AI brain and output around
DropsTab + Drops Bot.
DropsTab supplies market intelligence. Drops Bot supplies monitoring,
alerts and approved Telegram delivery. You own the source.
</p>
</div>
</div>
Expand Down
13 changes: 13 additions & 0 deletions app/platform/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Boxes, ShieldCheck } from "lucide-react";

import { PlatformOverview } from "@/components/platform/platform-overview";
import { PlatformShell } from "@/components/platform/platform-shell";
import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
import { platformCapabilitySnapshot } from "@/lib/platform-capabilities";

export const dynamic = "force-dynamic";

export default function PlatformPage() {
const snapshot = platformCapabilitySnapshot();
return <PlatformShell active="Platform"><PageIntro eyebrow="Drops platform" title="A crypto builder that shows its evidence." description="See what works locally, what needs provider configuration, and what remains feature-gated across Project V2, Sandbox, Drops intelligence, delivery, and the managed platform rollout." receipt={<><div className="flex items-center gap-3"><span className="grid size-11 place-items-center rounded-xl bg-[#eef4ff] text-[#245fe5]"><Boxes className="size-5" aria-hidden="true" /></span><div><strong className="text-sm">Capability-aware UI</strong><p className="mt-1 text-xs text-[#52617a]">Working, local, and setup states stay distinct</p></div></div><div className="mt-5 flex items-center justify-between gap-3"><StatusBadge status="working">Truthful states</StatusBadge><ShieldCheck className="size-5 text-[#139a62]" aria-hidden="true" /></div></>} /><PlatformOverview snapshot={snapshot} /></PlatformShell>;
}
9 changes: 9 additions & 0 deletions app/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FolderKanban, HardDrive } from "lucide-react";

import { PlatformShell } from "@/components/platform/platform-shell";
import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
import { ProjectLibrary } from "@/components/platform/project-library";

export default function ProjectsPage() {
return <PlatformShell active="Projects"><PageIntro eyebrow="Project workspace" title="Your real projects, one click from Studio." description="Open browser-saved products, inspect verified publication receipts, or start a new multi-file crypto project without invented sample activity." receipt={<><div className="flex items-center gap-3"><span className="grid size-11 place-items-center rounded-xl bg-[#eef4ff] text-[#245fe5]"><FolderKanban className="size-5" aria-hidden="true" /></span><div><strong className="text-sm">Browser-first library</strong><p className="mt-1 text-xs text-[#52617a]">Cloud sync remains account-dependent</p></div></div><div className="mt-5 flex items-center justify-between gap-3"><StatusBadge status="local">Local source of truth</StatusBadge><HardDrive className="size-5 text-[#245fe5]" aria-hidden="true" /></div></>} /><ProjectLibrary /></PlatformShell>;
}
1 change: 1 addition & 0 deletions app/styles/drops-studio.responsive.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
.studio-header nav.open { display: flex; align-items: stretch; }
.studio-header nav button, .studio-header nav a { border-radius: 8px; justify-content: space-between; padding: 11px; width: 100%; }
.studio-header nav button:hover, .studio-header nav a:hover { background: #f3f6fb; }
.studio-header nav .mobile-nav-connections { display: flex; }
.header-actions { min-width: 0; }
.mobile-menu { display: flex; }
.api-vault-button { display: none; }
Expand Down
2 changes: 1 addition & 1 deletion app/styles/drops-studio.shell.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
.studio-header nav button, .studio-header nav a { align-items: center; background: none; border: 0; color: #36435d; cursor: pointer; display: flex; font-size: 14px; font-weight: 560; gap: 6px; padding: 10px 0; text-decoration: none; }
.studio-header nav button:hover, .studio-header nav a:hover { color: var(--blue); }
.studio-header nav button span { align-items: center; background: #eaf0fb; border-radius: 99px; display: inline-flex; font-size: 12px; height: 18px; justify-content: center; min-width: 18px; }
.studio-header nav .mobile-nav-connections { display: none; }
.header-actions { align-items: center; display: flex; justify-content: flex-end; min-width: 218px; }
.api-vault-button { align-items: center; background: #fff; border: 1px solid #dbe3ef; border-radius: 11px; cursor: pointer; display: flex; font-size: 13px; font-weight: 650; gap: 7px; padding: 10px 14px; box-shadow: 0 5px 16px rgba(40, 60, 100, .05); }
.api-vault-button:hover { border-color: #adc4ff; color: var(--blue); }
.mobile-menu { background: transparent; border: 0; cursor: pointer; display: none; padding: 7px; }

5 changes: 5 additions & 0 deletions app/styles/platform-tailwind.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* Public dashboard routes are isolated from the legacy landing cascade. Keep
these utilities unlayered so explicit component classes can override the
older unlayered h1-h6 reset without changing the approved builder shell. */
@import "tailwindcss/theme.css";
@import "tailwindcss/utilities.css";
9 changes: 9 additions & 0 deletions app/templates/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Layers3, ShieldCheck } from "lucide-react";

import { PlatformShell } from "@/components/platform/platform-shell";
import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
import { TemplateCatalog } from "@/components/platform/template-catalog";

export default function TemplatesPage() {
return <PlatformShell active="Templates"><PageIntro eyebrow="12 production recipes" title="Start with crypto-native product depth." description="Browse the current Drops Studio foundations for intelligence, monitoring, creator, community, voice, and playable market experiences." receipt={<><div className="flex items-center gap-3"><span className="grid size-11 place-items-center rounded-xl bg-[#eef4ff] text-[#245fe5]"><Layers3 className="size-5" aria-hidden="true" /></span><div><strong className="text-sm">Current recipe catalog</strong><p className="mt-1 text-xs text-[#52617a]">Imported directly from lib/presets</p></div></div><div className="mt-5 flex items-center justify-between gap-3"><StatusBadge status="working">12 registered</StatusBadge><span className="flex items-center gap-1.5 text-xs text-[#596980]"><ShieldCheck className="size-4 text-[#139a62]" aria-hidden="true" />No placeholder presets</span></div></>} /><TemplateCatalog /></PlatformShell>;
}
33 changes: 17 additions & 16 deletions components/drops-studio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,10 +347,12 @@ function Brand() {

function useNearViewport() {
const [ready, setReady] = useState(false);
const elementRef = useRef<HTMLElement>(null);
const [element, setElement] = useState<HTMLElement | null>(null);
const elementRef = useCallback((next: HTMLElement | null) => {
setElement(next);
}, []);

useEffect(() => {
const element = elementRef.current;
if (!element) return;
const observer = new IntersectionObserver(
([entry]) => {
Expand All @@ -362,7 +364,7 @@ function useNearViewport() {
);
observer.observe(element);
return () => observer.disconnect();
}, []);
}, [element]);

return { ready, elementRef };
}
Expand Down Expand Up @@ -476,6 +478,13 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {

useEffect(() => {
const timer = window.setTimeout(async () => {
const params = new URLSearchParams(window.location.search);
const presetParam = params.get("preset");
const requestedCatalogPreset = presets.find(
(preset) => preset.id === presetParam,
);
if (requestedCatalogPreset) setSelectedId(requestedCatalogPreset.id);

let savedProjects = readProjectsFromStore();

try {
Expand Down Expand Up @@ -612,7 +621,6 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
} catch {
/* The local compiler remains available when access status is offline. */
}
const params = new URLSearchParams(window.location.search);
const handoff = parseStudioConnectionHandoff(window.location.search);
if (handoff.connections) {
const requestedProvider = providerList.find(
Expand Down Expand Up @@ -1701,17 +1709,7 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
<header className="studio-header">
<Brand />
<nav className={menuOpen ? "open" : ""} aria-label="Primary navigation">
<button
type="button"
onClick={() => {
document
.querySelector(".preset-section")
?.scrollIntoView({ behavior: "smooth" });
setMenuOpen(false);
}}
>
Templates
</button>
<a href="/templates">Templates</a>
<button
type="button"
onClick={() => {
Expand All @@ -1721,7 +1719,10 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
>
My Projects <span>{projects.length}</span>
</button>
<a href="/integrations">Integrations</a>
<a href="/platform">Platform</a>
<button
className="mobile-nav-connections"
type="button"
onClick={() => {
setConnectionOpen(true);
Expand Down Expand Up @@ -1965,7 +1966,7 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
/>
</section>

{previewSection.ready ? (
{previewSection.ready || selectedId !== defaultPresetId ? (
<PreviewCanvas
preset={selectedPreset}
spec={draftSpec ?? undefined}
Expand Down
Loading
Loading