Skip to content

Commit b9a26c4

Browse files
parth0025claude
andauthored
feat(demo): env-gated demo-mode banner (#244)
When DEMO_MODE=true on the server, the public brand-settings endpoint returns demoMode plus the shared demo credentials, and a thin top banner appears app-wide (and on the login page) showing the demo notice, the demo login, a Star-on-GitHub link, and a Deploy-your-own link. Fully off by default - with DEMO_MODE unset the flag is false, the banner renders nothing, and no credentials are exposed, so it is inert in real deployments. Closes the code portion of the S0-03 demo-instance task. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 16639f8 commit b9a26c4

6 files changed

Lines changed: 96 additions & 2 deletions

File tree

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@ LLM_MAX_TOKENS_CLARIFY=2000 # cap for follow-up Q&A
175175
# DEEPSEEK_TIMEOUT_MS=600000 # optional: raise DeepSeek axios timeout (default: 10 min for reasoner, 4 min for chat)
176176
# DEEPSEEK_BASE_URL="https://api.deepseek.com" # optional: override DeepSeek base URL (e.g. for a compatible proxy)
177177

178+
# ─────────────────────────────────────────
179+
# DEMO MODE (public demo instances only)
180+
# When DEMO_MODE=true, the app shows a "you're on the live demo" banner with
181+
# the shared demo credentials and a "deploy your own" link. Leave false for
182+
# real deployments. Only set DEMO_EMAIL/DEMO_PASSWORD on a throwaway demo DB.
183+
# ─────────────────────────────────────────
184+
DEMO_MODE="false"
185+
DEMO_EMAIL=""
186+
DEMO_PASSWORD=""
187+
178188
# ─────────────────────────────────────────
179189
# MISC
180190
# ─────────────────────────────────────────

Modules/Admin/common/controller.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,22 @@ exports.makeDefaultBrandSettings = () => {
9191

9292
exports.getBrandSettingsData = (req, res) => {
9393
try {
94+
// Public demo flag (+ shared demo creds) surfaced on this already-public,
95+
// pre-auth endpoint so the demo banner / login can read it without a
96+
// build-time var. Off unless DEMO_MODE=true in the server env.
97+
const demo = process.env.DEMO_MODE === 'true';
98+
const withDemo = (obj) => ({
99+
...obj,
100+
demoMode: demo,
101+
...(demo ? { demoEmail: process.env.DEMO_EMAIL || '', demoPassword: process.env.DEMO_PASSWORD || '' } : {}),
102+
});
103+
94104
const filePath = path.join(__dirname,'/../../../', 'brandSettings.json');
95105

96106
if (!fs.existsSync(filePath)) {
97107
exports.makeDefaultBrandSettings()
98108
.then((data) => {
99-
res.status(200).json(JSON.parse(data));
109+
res.status(200).json(withDemo(JSON.parse(data)));
100110
})
101111
.catch((error) => {
102112
res.status(404).send(error);
@@ -107,7 +117,7 @@ exports.getBrandSettingsData = (req, res) => {
107117
logger.error('Error writing file getBrandSettingsData:', err);
108118
return res.status(500).send('Internal Server Error');
109119
}
110-
res.status(200).json(JSON.parse(data));
120+
res.status(200).json(withDemo(JSON.parse(data)));
111121
});
112122
}
113123
} catch (error) {

frontend/src/App.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<template>
22
<div v-if="!underMaintainance">
3+
<DemoBanner/>
34
<template v-if="$route.meta.requiresAuth">
45
<template v-if="logged && (rules && Object.keys(rules).length && companyUserDetail && Object.keys(companyUserDetail).length) && socket">
56
<HeaderComponent v-if="!$route.meta.hideHeader" @change="changeCompany($event)" @filter="handleFilter"/>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<template>
2+
<div v-if="demoMode" class="demo-banner">
3+
<span class="demo-banner__msg">
4+
🚀 {{ $t('Demo.banner') }}
5+
<span v-if="demoEmail" class="demo-banner__creds">
6+
{{ $t('Demo.login_with') }} <strong>{{ demoEmail }}</strong><template v-if="demoPassword"> / <strong>{{ demoPassword }}</strong></template>
7+
</span>
8+
</span>
9+
<span class="demo-banner__links">
10+
<a :href="githubUrl" target="_blank" rel="noopener noreferrer">⭐ {{ $t('Demo.star') }}</a>
11+
<a :href="deployUrl" target="_blank" rel="noopener noreferrer">{{ $t('Demo.deploy') }}</a>
12+
</span>
13+
</div>
14+
</template>
15+
16+
<script setup>
17+
import { computed } from "vue";
18+
import { useStore } from "vuex";
19+
20+
const store = useStore();
21+
22+
const GITHUB_URL = 'https://github.com/aliansoftwareteam/AlianHub-Project-Management-System';
23+
const DEPLOY_URL = 'https://github.com/aliansoftwareteam/AlianHub-Project-Management-System#quick-start';
24+
25+
const brand = computed(() => store.getters['brandSettingTab/brandSettings'] || {});
26+
const demoMode = computed(() => brand.value.demoMode === true);
27+
const demoEmail = computed(() => brand.value.demoEmail || '');
28+
const demoPassword = computed(() => brand.value.demoPassword || '');
29+
const githubUrl = GITHUB_URL;
30+
const deployUrl = DEPLOY_URL;
31+
</script>
32+
33+
<style scoped>
34+
.demo-banner {
35+
display: flex;
36+
align-items: center;
37+
justify-content: center;
38+
flex-wrap: wrap;
39+
gap: 6px 16px;
40+
background: #2f3990;
41+
color: #fff;
42+
font-size: 12.5px;
43+
line-height: 1.3;
44+
padding: 6px 14px;
45+
text-align: center;
46+
}
47+
.demo-banner__creds {
48+
opacity: 0.92;
49+
margin-left: 6px;
50+
}
51+
.demo-banner__links {
52+
display: inline-flex;
53+
gap: 14px;
54+
white-space: nowrap;
55+
}
56+
.demo-banner__links a {
57+
color: #fff;
58+
text-decoration: underline;
59+
font-weight: 600;
60+
}
61+
.demo-banner__links a:hover {
62+
opacity: 0.85;
63+
}
64+
</style>

frontend/src/locales/en.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2655,4 +2655,10 @@ export default {
26552655
load_failed: "Couldn't load the changelog.",
26562656
retry: "Retry",
26572657
},
2658+
Demo: {
2659+
banner: "You're on the AlianHub live demo — data may reset periodically.",
2660+
login_with: "Log in with",
2661+
star: "Star on GitHub",
2662+
deploy: "Deploy your own",
2663+
},
26582664
};

frontend/src/main.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { DatePicker } from 'v-calendar';
1313
import 'v-calendar/style.css';
1414
import {i18n } from '@/locales/main';
1515
import VueApexCharts from "vue3-apexcharts";
16+
import DemoBanner from "@/components/atom/DemoBanner/DemoBanner.vue";
1617
// Plugins Path
1718
import registerPlugin from './plugins/register/registerPlugin';
1819
import createcompanyinsidePlugin from './plugins/createcompanyinside/createcompanyinsidePlugin';
@@ -61,6 +62,8 @@ for (let index = 0; index < pluginArray.length; index++) {
6162
app.use(ToastPlugin,{position: 'top-right'});
6263
app.use(plugin, defaultConfig({ plugins: [pro]}))
6364
app.use(i18n);
65+
// Registered before mount so the root App.vue template can resolve it.
66+
app.component('DemoBanner', DemoBanner);
6467
app.mount('#app');
6568
// Use plugin defaults (optional)
6669
app.component('ApexChart', VueApexCharts); // Register the apexchart component globally

0 commit comments

Comments
 (0)