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
4 changes: 4 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
VITE_GOOGLE_CLIENT_ID=your-client-id-here.apps.googleusercontent.com
VITE_GITHUB_CLIENT_ID=
VITE_BACKEND_URL=http://localhost:8000

# Toggle premium-tier UI (Pro plan, Desktop download, MCP, sign-in/account).
# Defaults to off (public launch); set to true to surface these features.
VITE_SHOW_PREMIUM=false
3 changes: 3 additions & 0 deletions frontend/src/boxes/sql/SqlBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import { DATABASE_INFO } from '../../types/database'
import { hasCTEs } from '../../utils/cteParser'
import { useSqlGlotStore } from '../../stores/sqlglot'
import { useUserStore } from '../../stores/user'
import { SHOW_PREMIUM } from '../../constants/features'

const duckdbStore = useDuckDBStore()
const canvasStore = useCanvasStore()
const sqlglotStore = useSqlGlotStore()
const queryHistoryStore = useQueryHistoryStore()
const settingsStore = useSettingsStore()
const userStore = useUserStore()
const showPremium = SHOW_PREMIUM

// Inject box executor registry for recursive dependency execution
const registerBoxExecutor = inject<((boxId: number, runFn: () => Promise<void>) => void) | null>('registerBoxExecutor', null)
Expand Down Expand Up @@ -509,6 +511,7 @@ defineExpose({

<!-- Wand (AI spell) -->
<button
v-if="showPremium"
v-tooltip="wandButtonTooltip"
class="header-action-btn"
:class="{ active: panelShowSpellInput, casting: panelIsCastingSpell }"
Expand Down
38 changes: 24 additions & 14 deletions frontend/src/components/MenuBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ import { refreshSchemaCache } from '../utils/schemaAdapter'
import ClickHouseConnectionModal from './ClickHouseConnectionModal.vue'
import SnowflakeConnectionModal from './SnowflakeConnectionModal.vue'
import { isTauri } from '../utils/tauri'
import { SHOW_PREMIUM } from '../constants/features'

// Web-only features (accounts, billing) are hidden in the desktop app.
const isWebApp = !isTauri()
// Premium UI (sign-in, Pro badge, MCP, Share) is hidden for the public launch.
const showPremium = SHOW_PREMIUM
import SettingsPanel from './SettingsPanel.vue'
import BigQueryOAuthModal from './BigQueryOAuthModal.vue'
import CopyButton from './CopyButton.vue'
Expand Down Expand Up @@ -590,15 +593,17 @@ onUnmounted(() => {
:disabled="!canvasStore.activeCanvasId"
@click="handleRenameActive"
>Rename canvas...</button>
<div class="dropdown-divider"></div>
<button
class="dropdown-item"
:disabled="!userStore.isPro || !canvasStore.activeCanvasId"
@click="showShareDialog = true; activeDropdown = null"
>
Share...
<span v-if="!userStore.isPro" class="item-hint">(Pro)</span>
</button>
<template v-if="showPremium">
<div class="dropdown-divider"></div>
<button
class="dropdown-item"
:disabled="!userStore.isPro || !canvasStore.activeCanvasId"
@click="showShareDialog = true; activeDropdown = null"
>
Share...
<span v-if="!userStore.isPro" class="item-hint">(Pro)</span>
</button>
</template>
<div class="dropdown-divider"></div>
<button
class="dropdown-item dropdown-item-danger"
Expand Down Expand Up @@ -960,7 +965,11 @@ onUnmounted(() => {
</button>
<Transition name="dropdown">
<div v-if="activeDropdown === 'tools'" class="dropdown os-dropdown">
<button class="dropdown-item" @click="showMcpModal = true; activeDropdown = null">
<button
v-if="showPremium"
class="dropdown-item"
@click="showMcpModal = true; activeDropdown = null"
>
Connect via MCP...
</button>
<button class="dropdown-item" @click="handleRefreshSchemas">Refresh schemas</button>
Expand All @@ -979,11 +988,11 @@ onUnmounted(() => {

<div class="menu-right">
<!-- Pro Badge -->
<span v-if="userStore.isPro" class="pro-badge menu-pro-badge">Pro</span>
<span v-if="showPremium && userStore.isPro" class="pro-badge menu-pro-badge">Pro</span>

<!-- User Menu (web only — desktop has no Squill accounts) -->
<div
v-if="userStore.isLoggedIn && isWebApp"
v-if="showPremium && userStore.isLoggedIn && isWebApp"
class="menu-item user-menu-item"
>
<button
Expand Down Expand Up @@ -1027,7 +1036,7 @@ onUnmounted(() => {

<!-- Sign In Dropdown (web only — desktop has no Squill accounts) -->
<div
v-else-if="isWebApp"
v-else-if="showPremium && isWebApp"
class="menu-item sign-in-menu-item"
>
<button
Expand Down Expand Up @@ -1088,12 +1097,13 @@ onUnmounted(() => {

<!-- Share Dialog (Pro only) -->
<ShareDialog
v-if="showPremium"
:show="showShareDialog"
@close="showShareDialog = false"
/>

<!-- MCP Setup Modal -->
<Teleport to="body">
<Teleport v-if="showPremium" to="body">
<Transition name="dropdown">
<div v-if="showMcpModal" class="modal-overlay" @click.self="showMcpModal = false">
<div class="modal-content mcp-modal">
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/SettingsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { useSettingsStore } from '../stores/settings'
import { useUserStore } from '../stores/user'
import { useDialog } from '../composables/useDialog'
import { isTauri } from '../utils/tauri'
import { SHOW_PREMIUM } from '../constants/features'
import BigQueryOAuthModal from './BigQueryOAuthModal.vue'

const { confirm } = useDialog()
const isDesktop = isTauri()
const showPremium = SHOW_PREMIUM
const showBigQueryOAuthModal = ref(false)

defineProps<{
Expand Down Expand Up @@ -175,7 +177,7 @@ const handleResetAll = async () => {
</div>
</div>

<div class="settings-section">
<div v-if="showPremium" class="settings-section">
<div class="setting-header">
Hex remover
<span
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/constants/features.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Master toggle for premium-tier UI: Pro plan, Desktop download, MCP server,
// and the sign-in / account flow (which today only buys Pro features).
// Set VITE_SHOW_PREMIUM=true to surface them; defaults to off for the public
// launch. Granular flags can be split out later if needed.
export const SHOW_PREMIUM: boolean = import.meta.env.VITE_SHOW_PREMIUM === 'true'
11 changes: 11 additions & 0 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import App from './App.vue'
import type { RouteRecordRaw } from 'vue-router'
import { vTooltip, vTooltipOverflow } from './directives/tooltip'
import { isTauri } from './utils/tauri'
import { SHOW_PREMIUM } from './constants/features'
import './boxes' // Register all box modules

// Eagerly load landing page for fast initial render
Expand Down Expand Up @@ -71,5 +72,15 @@ export const createApp = ViteSSG(
if (to.path === '/') return '/app'
})
}

// Premium-only routes (Squill account, MCP consent) are hidden during the
// public launch. /auth/callback is intentionally not blocked — BigQuery's
// OAuth flow shares the same callback. Redirect any direct hits to the app.
if (!SHOW_PREMIUM) {
const premiumOnlyPaths = new Set(['/account', '/oauth/consent'])
router.beforeEach((to) => {
if (premiumOnlyPaths.has(to.path)) return '/app'
})
}
}
)
78 changes: 42 additions & 36 deletions frontend/src/views/LandingPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useHead } from '@unhead/vue'
import { renderMarkdown } from '../utils/markdown'
import { DATABASE_ENGINES, DATABASE_INFO, type DatabaseEngine, type DatabaseInfo } from '../types/database'
import { changelog } from '../data/changelog'
import { SHOW_PREMIUM } from '../constants/features'

const router = useRouter()

Expand Down Expand Up @@ -127,18 +128,12 @@ const closeProFeatureModal = () => {
}

// FAQ data (database-specific entries removed - now shown in database modal)
const faqs = [
const baseFaqs = [
{
question: 'Is my data safe?',
answer: 'Yes. All queries run client-side — directly from your browser to the database API. Your data and results never pass through Squill servers. The web app stores credentials in browser storage (IndexedDB). For stronger security, Squill Desktop stores credentials in your OS keychain (macOS Keychain / Windows Credential Manager), which is encrypted at rest and protected by your OS login.'
},
{
question: 'Why would I want Squill Desktop?',
answer: 'Squill Desktop stores credentials in your OS keychain instead of browser storage — the same approach used by DataGrip and DBeaver. You also get native performance, lower memory usage, and no browser tab to manage.'
},
{
question: 'Do I need to create an account?',
answer: 'No, you can start querying right away without an account. Creating an account lets you upgrade to Squill Pro for cloud saves, collaboration, and AI features. You do not need an account to process CSV files and play with DuckDB.'
answer: SHOW_PREMIUM
? 'Yes. All queries run client-side — directly from your browser to the database API. Your data and results never pass through Squill servers. The web app stores credentials in browser storage (IndexedDB). For stronger security, Squill Desktop stores credentials in your OS keychain (macOS Keychain / Windows Credential Manager), which is encrypted at rest and protected by your OS login.'
: 'Yes. All queries run client-side — directly from your browser to the database API. Your data and results never pass through Squill servers. Credentials are stored in browser storage (IndexedDB) on your machine.'
},
{
question: 'Can I query CSV files with SQL?',
Expand All @@ -151,13 +146,26 @@ const faqs = [
{
question: 'Is Squill open source?',
answer: 'Yes, Squill is fully open source under the AGPL license. You can inspect the code, contribute, or self-host it. The source code is available on GitHub.'
}
]

const premiumFaqs = [
{
question: 'Why would I want Squill Desktop?',
answer: 'Squill Desktop stores credentials in your OS keychain instead of browser storage — the same approach used by DataGrip and DBeaver. You also get native performance, lower memory usage, and no browser tab to manage.'
},
{
question: 'Do I need to create an account?',
answer: 'No, you can start querying right away without an account. Creating an account lets you upgrade to Squill Pro for cloud saves, collaboration, and AI features. You do not need an account to process CSV files and play with DuckDB.'
},
{
question: 'Why 8€/month for Pro?',
answer: 'The paid features involve compute costs: LLM tokens for fixing queries, storage costs for saving canvases, and server costs for collaboration. There are no vanity features.'
}
]

const faqs = SHOW_PREMIUM ? [...baseFaqs, ...premiumFaqs] : baseFaqs

// Desktop download
const DESKTOP_VERSION = '0.2.1'
const RELEASE_BASE = 'https://github.com/MaxHalford/squill/releases/latest/download'
Expand Down Expand Up @@ -203,30 +211,27 @@ const webApplicationSchema = {
'@type': 'WebApplication',
name: 'Squill',
url: 'https://squill.dev',
description: 'Open source SQL canvas in the browser. Query CSV files with DuckDB, connect to BigQuery, ClickHouse, and Snowflake. All queries run client-side. Infinite canvas, drag-and-drop CSV, AI query fixer. No install needed.',
description: 'Open source SQL canvas in the browser. Query CSV files with DuckDB, connect to BigQuery, ClickHouse, and Snowflake. All queries run client-side. Infinite canvas, drag-and-drop CSV. No install needed.',
applicationCategory: 'DeveloperApplication',
operatingSystem: 'Any',
browserRequirements: 'Requires JavaScript, WebAssembly support',
offers: [
{
'@type': 'Offer',
price: '0',
priceCurrency: 'EUR',
name: 'Free'
},
{
'@type': 'Offer',
price: '8',
priceCurrency: 'EUR',
name: 'Pro',
priceSpecification: {
'@type': 'UnitPriceSpecification',
price: '8',
priceCurrency: 'EUR',
billingDuration: 'P1M'
}
}
],
offers: SHOW_PREMIUM
? [
{ '@type': 'Offer', price: '0', priceCurrency: 'EUR', name: 'Free' },
{
'@type': 'Offer',
price: '8',
priceCurrency: 'EUR',
name: 'Pro',
priceSpecification: {
'@type': 'UnitPriceSpecification',
price: '8',
priceCurrency: 'EUR',
billingDuration: 'P1M'
}
}
]
: [{ '@type': 'Offer', price: '0', priceCurrency: 'EUR', name: 'Free' }],
featureList: [
'Infinite canvas for SQL queries',
'DuckDB WebAssembly support',
Expand All @@ -237,8 +242,7 @@ const webApplicationSchema = {
'Drag and drop CSV file analysis',
'Schema browser',
'Go to definition (Cmd+click)',
'AI SQL line fixer (Pro)',
'MCP server for AI agents (Pro)'
...(SHOW_PREMIUM ? ['AI SQL line fixer (Pro)', 'MCP server for AI agents (Pro)'] : [])
]
}

Expand All @@ -261,7 +265,9 @@ useHead({
meta: [
{
name: 'description',
content: 'Open source SQL editor that runs in your browser. Query CSV files with DuckDB, connect to BigQuery, ClickHouse, and Snowflake. All queries run client-side. Infinite canvas, drag-and-drop CSV, schema browser, AI query fixer. No install needed.'
content: SHOW_PREMIUM
? 'Open source SQL editor that runs in your browser. Query CSV files with DuckDB, connect to BigQuery, ClickHouse, and Snowflake. All queries run client-side. Infinite canvas, drag-and-drop CSV, schema browser, AI query fixer. No install needed.'
: 'Open source SQL editor that runs in your browser. Query CSV files with DuckDB, connect to BigQuery, ClickHouse, and Snowflake. All queries run client-side. Infinite canvas, drag-and-drop CSV, schema browser. No install needed.'
},
// Open Graph
{ property: 'og:title', content: 'Squill - Open Source SQL Canvas' },
Expand Down Expand Up @@ -844,7 +850,7 @@ const latestChangelogHtml = latestChangelog ? renderMarkdown(latestChangelog.con
</Teleport>

<!-- Desktop Section -->
<section class="section desktop">
<section v-if="SHOW_PREMIUM" class="section desktop">
<h2 class="section-title">
SQUILL DESKTOP
</h2>
Expand Down Expand Up @@ -919,7 +925,7 @@ const latestChangelogHtml = latestChangelog ? renderMarkdown(latestChangelog.con
</Teleport>

<!-- Pro Section -->
<section class="section section-inverted pro">
<section v-if="SHOW_PREMIUM" class="section section-inverted pro">
<div class="pro-container">
<h2 class="section-title">
SQUILL PRO
Expand Down
Loading