Merge branch 'feature/dynamic-promo-widget'

This commit is contained in:
SPRINX0\prochazka
2025-10-20 15:28:29 +02:00
6 changed files with 91 additions and 52 deletions

View File

@@ -8,6 +8,7 @@ const {
getCloudContent, getCloudContent,
putCloudContent, putCloudContent,
removeCloudCachedConnection, removeCloudCachedConnection,
getPromoWidgetData,
} = require('../utility/cloudIntf'); } = require('../utility/cloudIntf');
const connections = require('./connections'); const connections = require('./connections');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
@@ -283,6 +284,18 @@ module.exports = {
return getAiGatewayServer(); return getAiGatewayServer();
}, },
premiumPromoWidget_meta: true,
async premiumPromoWidget() {
const data = getPromoWidgetData();
if (data?.state != 'data') {
return null;
}
if (data.validTo && new Date().getTime() > new Date(data.validTo).getTime()) {
return null;
}
return data;
},
// chatStream_meta: { // chatStream_meta: {
// raw: true, // raw: true,
// method: 'post', // method: 'post',

View File

@@ -17,6 +17,7 @@ const currentVersion = require('../currentVersion');
const logger = getLogger('cloudIntf'); const logger = getLogger('cloudIntf');
let cloudFiles = null; let cloudFiles = null;
let promoWidgetData = null;
const DBGATE_IDENTITY_URL = process.env.LOCAL_DBGATE_IDENTITY const DBGATE_IDENTITY_URL = process.env.LOCAL_DBGATE_IDENTITY
? 'http://localhost:3103' ? 'http://localhost:3103'
@@ -259,6 +260,36 @@ async function getPublicFileData(path) {
return resp.data; return resp.data;
} }
async function updatePremiumPromoWidget() {
try {
const fileContent = await fs.readFile(path.join(datadir(), 'promo-widget.json'), 'utf-8');
promoWidgetData = JSON.parse(fileContent);
} catch (err) {
promoWidgetData = null;
}
const tags = (await collectCloudFilesSearchTags()).join(',');
const resp = await axios.default.get(
`${DBGATE_CLOUD_URL}/premium-promo-widget?identifier=${promoWidgetData?.identifier ?? 'empty'}&tags=${tags}`,
{
headers: {
...(await getCloudInstanceHeaders()),
'x-app-version': currentVersion.version,
},
}
);
if (!resp.data || resp.data?.state == 'unchanged') {
return;
}
promoWidgetData = resp.data;
await fs.writeFile(path.join(datadir(), 'promo-widget.json'), JSON.stringify(promoWidgetData, null, 2));
socket.emitChanged(`promo-widget-changed`);
}
async function refreshPublicFiles(isRefresh) { async function refreshPublicFiles(isRefresh) {
if (!cloudFiles) { if (!cloudFiles) {
await loadCloudFiles(); await loadCloudFiles();
@@ -268,6 +299,9 @@ async function refreshPublicFiles(isRefresh) {
} catch (err) { } catch (err) {
logger.error(extractErrorLogData(err), 'DBGM-00166 Error updating cloud files'); logger.error(extractErrorLogData(err), 'DBGM-00166 Error updating cloud files');
} }
if (!isProApp()) {
await updatePremiumPromoWidget();
}
} }
async function callCloudApiGet(endpoint, signinHolder = null, additionalHeaders = {}) { async function callCloudApiGet(endpoint, signinHolder = null, additionalHeaders = {}) {
@@ -432,6 +466,10 @@ async function getPublicIpInfo() {
} }
} }
function getPromoWidgetData() {
return promoWidgetData;
}
module.exports = { module.exports = {
createDbGateIdentitySession, createDbGateIdentitySession,
startCloudTokenChecking, startCloudTokenChecking,
@@ -449,4 +487,5 @@ module.exports = {
readCloudTokenHolder, readCloudTokenHolder,
readCloudTestTokenHolder, readCloudTestTokenHolder,
getPublicIpInfo, getPublicIpInfo,
getPromoWidgetData,
}; };

View File

@@ -183,6 +183,7 @@ export const focusedConnectionOrDatabase = writable<{ conid: string; database?:
export const focusedTreeDbKey = writable<{ key: string; root: string; type: string; text: string }>(null); export const focusedTreeDbKey = writable<{ key: string; root: string; type: string; text: string }>(null);
export const cloudSigninTokenHolder = writableSettingsValue(null, 'cloudSigninTokenHolder'); export const cloudSigninTokenHolder = writableSettingsValue(null, 'cloudSigninTokenHolder');
export const seenPremiumPromoWidget = writableWithStorage(null, 'seenPremiumPromoWidget');
export const cloudConnectionsStore = writable({}); export const cloudConnectionsStore = writable({});

View File

@@ -183,12 +183,16 @@ const cloudContentListLoader = () => ({
params: {}, params: {},
reloadTrigger: { key: `cloud-content-changed` }, reloadTrigger: { key: `cloud-content-changed` },
}); });
const teamFilesLoader = () => ({ const teamFilesLoader = () => ({
url: 'team-files/list', url: 'team-files/list',
params: {}, params: {},
reloadTrigger: { key: `team-files-changed` }, reloadTrigger: { key: `team-files-changed` },
}); });
const promoWidgetLoader = () => ({
url: 'cloud/premium-promo-widget',
params: {},
reloadTrigger: { key: `promo-widget-changed` },
});
async function getCore(loader, args) { async function getCore(loader, args) {
const { url, params, reloadTrigger, transform, onLoaded, errorValue } = loader(args); const { url, params, reloadTrigger, transform, onLoaded, errorValue } = loader(args);
@@ -536,3 +540,10 @@ export function getTeamFiles(args) {
export function useTeamFiles(args) { export function useTeamFiles(args) {
return useCore(teamFilesLoader, args); return useCore(teamFilesLoader, args);
} }
export function getPromoWidget(args) {
return getCore(promoWidgetLoader, args);
}
export function usePromoWidget(args) {
return useCore(promoWidgetLoader, args);
}

View File

@@ -1,59 +1,15 @@
<script lang='ts'> <script lang="ts">
import JsonUiContentRenderer from '../jsonui/JsonUiContentRenderer.svelte'; import JsonUiContentRenderer from '../jsonui/JsonUiContentRenderer.svelte';
import { JsonUiBlock } from '../jsonui/jsonuitypes'; import { usePromoWidget } from '../utility/metadataLoaders';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte'; import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
const data: JsonUiBlock[] = [ const promoWidget = usePromoWidget({});
{ type: 'heading', text: 'Try DbGate Premium' },
{ type: 'text', text: 'Upgrade to get exclusive features:' },
{
type: 'ticklist',
items: [
'Query designer',
'AI powered database chat',
'Unlimited DbGate Cloud storage',
'Shared cloud folders',
'Charts from query result',
'Compare database models',
'Synchronize database structure',
'Backup & restore database',
'Advanced ER diagram settings',
'Export database model',
'Firestore, libSQL, Turso, CosmosDB, Redshift support',
'Amazon and Azure identity providers',
'E-mail support',
],
},
{ type: 'heading', text: 'Download DbGate Premium' },
{
type: 'ticklist',
items: ['Free 30 day trial', 'DbGate Premium will reuse your connections and files from DbGate Community'],
},
{ type: 'button', text: 'Download', link: 'https://www.dbgate.io/download' },
{ type: 'heading', text: 'Purchase DbGate Premium' },
{
type: 'ticklist',
items: ['Use monthly or yearly subscription'],
},
{ type: 'button', text: 'Purchase', link: 'https://www.dbgate.io/purchase/premium' },
{ type: 'heading', text: 'Get PREMIUM license for free' },
{
type: 'text',
text: 'Your feedback is very valuable for us. We have time-limited offers available for users who provide feedback.',
},
{
type: 'button',
text: 'View current offer',
link: 'https://www.dbgate.io/review?utm_campaign=communityWidget',
},
];
</script> </script>
<WidgetsInnerContainer> <WidgetsInnerContainer>
<JsonUiContentRenderer blocks={data} /> {#if $promoWidget?.state == 'data'}
<JsonUiContentRenderer blocks={$promoWidget?.blocks} />
{/if}
</WidgetsInnerContainer> </WidgetsInnerContainer>
<style> <style>

View File

@@ -10,6 +10,7 @@
lockedDatabaseMode, lockedDatabaseMode,
getCurrentConfig, getCurrentConfig,
cloudSigninTokenHolder, cloudSigninTokenHolder,
seenPremiumPromoWidget,
} from '../stores'; } from '../stores';
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition'; import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
import hasPermission from '../utility/hasPermission'; import hasPermission from '../utility/hasPermission';
@@ -20,11 +21,14 @@
import { showModal } from '../modals/modalTools'; import { showModal } from '../modals/modalTools';
import NewObjectModal from '../modals/NewObjectModal.svelte'; import NewObjectModal from '../modals/NewObjectModal.svelte';
import openNewTab from '../utility/openNewTab'; import openNewTab from '../utility/openNewTab';
import { usePromoWidget } from '../utility/metadataLoaders';
let domSettings; let domSettings;
let domCloudAccount; let domCloudAccount;
let domMainMenu; let domMainMenu;
const promoWidget = usePromoWidget({});
const widgets = [ const widgets = [
getCurrentConfig().storageDatabase && { getCurrentConfig().storageDatabase && {
icon: 'icon admin', icon: 'icon admin',
@@ -98,6 +102,10 @@
} else { } else {
$selectedWidget = name; $selectedWidget = name;
$visibleWidgetSideBar = true; $visibleWidgetSideBar = true;
if (name == 'premium') {
$seenPremiumPromoWidget = $promoWidget?.identifier || '';
}
} }
} }
//const handleChangeWidget= e => (selectedWidget.set(item.name)) //const handleChangeWidget= e => (selectedWidget.set(item.name))
@@ -169,7 +177,7 @@
{/if} {/if}
{#each widgets {#each widgets
.filter(x => x && hasPermission(`widgets/${x.name}`)) .filter(x => x && hasPermission(`widgets/${x.name}`))
.filter(x => !x.isPremiumPromo || !isProApp()) .filter(x => !x.isPremiumPromo || (!isProApp() && $promoWidget?.state == 'data'))
// .filter(x => !x.isPremiumOnly || isProApp()) // .filter(x => !x.isPremiumOnly || isProApp())
.filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item} .filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item}
<div <div
@@ -181,6 +189,9 @@
<FontIcon icon={item.icon} title={item.title} /> <FontIcon icon={item.icon} title={item.title} />
{#if item.isPremiumPromo} {#if item.isPremiumPromo}
<div class="premium-promo">Premium</div> <div class="premium-promo">Premium</div>
{#if $promoWidget?.identifier != $seenPremiumPromoWidget}
<div class="premium-promo-not-seen">•</div>
{/if}
{/if} {/if}
</div> </div>
{/each} {/each}
@@ -262,4 +273,12 @@
border-radius: 3px; border-radius: 3px;
bottom: 0; bottom: 0;
} }
.premium-promo-not-seen {
position: absolute;
font-size: 16pt;
color: var(--theme-icon-yellow);
top: -5px;
right: 5px;
}
</style> </style>