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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ LISTMONK_WELCOME_TEMPLATE_ID="1"
BRAND_NAME="Crove Post"
# Short application brand name
BRAND_SHORT_NAME="Crove"
# Machine identifier for the MCP server in generated client config
# (`claude mcp add <name>`, `{ mcpServers: { <name>: … } }`, Cursor deep links).
# Must be a lowercase hyphenated slug. Leave EMPTY to derive it automatically
# from BRAND_SHORT_NAME ("Crove" -> "crove", "Crove (Beta)" -> "crove-beta").
BRAND_MCP_CONNECTOR_NAME=""
# Brand meta description
BRAND_DESCRIPTION="The AI-powered multi-channel social media management platform"
# Company legal entity name
Expand Down Expand Up @@ -181,6 +186,10 @@ BRAND_EXTENSION_STORE_URL="https://chromewebstore.google.com/detail/sample-exten
BRAND_TUTORIAL_URL="https://example.com/tutorials"
# Affiliate program URL
BRAND_AFFILIATE_URL="https://example.com/affiliates"
# Claude connector directory listing URL. Leave EMPTY to hide the
# "Add to Claude" button — it defaults to empty on purpose so a deployment
# that forgets to set it cannot offer the upstream listing by accident.
BRAND_CLAUDE_DIRECTORY_URL=""

# ==============================================================================
# 8. Single Sign-On (Generic OAuth 2.0 / DOS ID via PKCE Bridge)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { useDecisionModal, useModals } from '@gitroom/frontend/components/layout
import { MediaBox } from '@gitroom/frontend/components/media/media.component';
import copy from 'copy-to-clipboard';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
import { useVariables } from '@gitroom/react/helpers/variable.context';
import { joinBrandUrl } from '@gitroom/helpers/utils/brand.config';

const useOAuthApp = () => {
const fetch = useFetch();
Expand Down Expand Up @@ -67,6 +69,7 @@ export const DeveloperComponent: FC = () => {
const decision = useDecisionModal();
const modals = useModals();
const t = useT();
const { brandConfig } = useVariables();
const { data: app, mutate } = useOAuthApp();
const [plaintextSecret, setPlaintextSecret] = useState<string | null>(null);
const [creating, setCreating] = useState(false);
Expand Down Expand Up @@ -244,7 +247,7 @@ export const DeveloperComponent: FC = () => {
<div className="flex gap-[6px] shrink-0 pt-[2px]">
<a
className="cursor-pointer px-[16px] h-[36px] bg-[#612BD3] hover:bg-[#5520CB] text-white transition-colors rounded-[8px] text-[13px] font-[600] flex items-center gap-[6px]"
href="https://docs.postiz.com/public-api/oauth"
href={joinBrandUrl(brandConfig?.docsUrl, 'public-api/oauth')}
target="_blank"
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" /></svg>
Expand Down Expand Up @@ -407,7 +410,7 @@ export const DeveloperComponent: FC = () => {
<div className="flex gap-[6px] shrink-0 pt-[2px]">
<a
className="cursor-pointer px-[16px] h-[36px] bg-[#612BD3] hover:bg-[#5520CB] text-white transition-colors rounded-[8px] text-[13px] font-[600] flex items-center gap-[6px]"
href="https://docs.postiz.com/public-api/oauth"
href={joinBrandUrl(brandConfig?.docsUrl, 'public-api/oauth')}
target="_blank"
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" /></svg>
Expand Down
7 changes: 5 additions & 2 deletions apps/frontend/src/components/layout/top.menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface MenuItemInterface {
}

export const useMenuItem = () => {
const { isGeneral } = useVariables();
const { isGeneral, brandConfig } = useVariables();
const t = useT();
const { openModal } = useModals();

Expand Down Expand Up @@ -246,7 +246,10 @@ export const useMenuItem = () => {
/>
</svg>
),
path: 'https://affiliate.postiz.com',
path: brandConfig?.affiliateUrl || '#',
// No affiliate program configured for this deployment — hide the entry
// rather than sending customers to the upstream program.
hide: !brandConfig?.affiliateUrl,
role: ['ADMIN', 'SUPERADMIN', 'USER'],
requireBilling: true,
},
Expand Down
35 changes: 24 additions & 11 deletions apps/frontend/src/components/onboarding/onboarding.modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, { FC, Fragment, useCallback, useMemo, useState } from 'react';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { joinBrandUrl } from '@gitroom/helpers/utils/brand.config';
import useSWR from 'swr';
import { orderBy } from 'lodash';
import clsx from 'clsx';
Expand Down Expand Up @@ -274,13 +275,23 @@ const otherAgents = mcpClients.filter(
const apiTab = 'API' as const;
type OnboardingTab = OnboardingAgent | typeof otherTab | typeof apiTab;

const cliCommands = localCliSteps.map((step) => step.code);
// localCliSteps carries {API_URL} / {API_KEY} placeholders. This panel
// substitutes the instance URL but deliberately not the key: the API tab
// already reveals it behind its own toggle, and echoing the secret into a
// second panel puts it in screenshots and screen-shares.
const getCliCommands = (apiBaseUrl: string) =>
localCliSteps.map((step) =>
step.code
.replace('{API_URL}', apiBaseUrl)
.replace('{API_KEY}', '<your API key>')
);

// Cursor one-click install: https://cursor.com/docs/mcp/install-links
const getCursorInstallUrl = (
auth: McpAuth,
mcpBase: string,
apiKey: string
apiKey: string,
connectorName: string
) => {
const server =
auth === 'oauth'
Expand All @@ -289,9 +300,9 @@ const getCursorInstallUrl = (
url: `${mcpBase}/mcp`,
headers: { Authorization: `Bearer ${apiKey}` },
};
return `cursor://anysphere.cursor-deeplink/mcp/install?name=postiz&config=${btoa(
JSON.stringify(server)
)}`;
return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(
connectorName
)}&config=${btoa(JSON.stringify(server))}`;
};

const OnboardingStep2: FC<{ onBack: () => void; onNext: () => void }> = ({
Expand All @@ -300,7 +311,7 @@ const OnboardingStep2: FC<{ onBack: () => void; onNext: () => void }> = ({
}) => {
const t = useT();
const user = useUser();
const { backendUrl, mcpUrl, billingEnabled } = useVariables();
const { backendUrl, mcpUrl, billingEnabled, brandConfig } = useVariables();
const [tab, setTab] = useState<OnboardingTab>('Claude');
const [otherAgent, setOtherAgent] = useState<McpClient>(otherAgents[0]);
// The client the cards describe: the tab itself, or the pick inside "Other agents"
Expand All @@ -311,11 +322,13 @@ const OnboardingStep2: FC<{ onBack: () => void; onNext: () => void }> = ({
const mcpBase = mcpUrl || backendUrl;
const apiKey = user?.publicApi || '';
const available = !!apiKey && !!user?.tier?.public_api;
const cliCommands = getCliCommands(mcpBase);
const connectorName = brandConfig?.mcpConnectorName || 'mcp';

const { config, hint } =
agent === apiTab
? { config: '', hint: '' }
: getMcpConfig(agent, auth, mcpBase, apiKey);
: getMcpConfig(agent, auth, mcpBase, apiKey, connectorName);

const maskedConfig =
revealed || auth === 'oauth' || !apiKey
Expand All @@ -326,14 +339,14 @@ const OnboardingStep2: FC<{ onBack: () => void; onNext: () => void }> = ({
);

const connector =
agent === 'Claude' && billingEnabled
agent === 'Claude' && billingEnabled && brandConfig?.claudeDirectoryUrl
? {
href: 'https://claude.ai/directory/postiz',
href: brandConfig.claudeDirectoryUrl,
label: t('add_to_claude', 'Add to Claude'),
}
: agent === 'Cursor'
? {
href: getCursorInstallUrl(auth, mcpBase, apiKey),
href: getCursorInstallUrl(auth, mcpBase, apiKey, connectorName),
label: t('add_to_cursor', 'Add to Cursor'),
}
: null;
Expand Down Expand Up @@ -398,7 +411,7 @@ const OnboardingStep2: FC<{ onBack: () => void; onNext: () => void }> = ({
</div>
<a
className="cursor-pointer px-[24px] h-[44px] bg-[#612BD3] hover:bg-[#5520CB] text-white transition-colors rounded-[8px] text-[14px] font-[600] flex items-center gap-[8px] shrink-0"
href="https://docs.postiz.com/public-api/introduction"
href={joinBrandUrl(brandConfig?.docsUrl, 'public-api/introduction')}
target="_blank"
>
<McpClientIcon client={apiTab} size={18} />
Expand Down
Loading
Loading