diff --git a/frontend/src/lib/components/header/CompanyBanner.svelte b/frontend/src/lib/components/header/CompanyBanner.svelte
index 0673e22..d48fa29 100644
--- a/frontend/src/lib/components/header/CompanyBanner.svelte
+++ b/frontend/src/lib/components/header/CompanyBanner.svelte
@@ -1,25 +1,44 @@
-{#if isCompanyView}
+{#if isCompanyView || hasContextMismatch}
-
+
-
- Viewing as
-
- {context.companyName}
-
-
+ {#if hasContextMismatch}
+
+
+ {#if isCompanyView && isResourceGlobal}
+
Viewing as
+
{context.companyName}
+
•
+
+ This {resource.resourceType || 'resource'} is
+ global
+
+ {:else if !isCompanyView && resource.resourceCompanyID}
+
+ This {resource.resourceType || 'resource'} belongs to
+ {resource.resourceCompanyName || 'a company'}
+
+ {:else if isResourceInDifferentCompany}
+
Viewing as
+
{context.companyName}
+
•
+
+ This {resource.resourceType || 'resource'} belongs to
+ {resource.resourceCompanyName || 'another company'}
+
+ {/if}
-
-
+
+
+
+ {:else}
+
+
+ Viewing as
+
+ {context.companyName}
+
+
+
+
+
+ {/if}
{/if}
-
-{#if isCompanyView}
+
+{#if isCompanyView || hasContextMismatch}
{/if}
@@ -81,7 +182,7 @@
left: 0;
right: 0;
bottom: 0;
- border: 3px solid #1e3fa8;
+ border: 3px solid;
pointer-events: none;
z-index: 9999;
}
diff --git a/frontend/src/lib/store/resourceContext.js b/frontend/src/lib/store/resourceContext.js
new file mode 100644
index 0000000..be7a5bb
--- /dev/null
+++ b/frontend/src/lib/store/resourceContext.js
@@ -0,0 +1,45 @@
+import { writable } from 'svelte/store';
+
+/**
+ * store for tracking the current resource being viewed
+ * this allows the company banner to show context mismatch information
+ */
+function createResourceContextStore() {
+ const { subscribe, set, update } = writable({
+ resourceType: null, // e.g., 'campaign', 'template', 'email'
+ resourceCompanyID: null,
+ resourceCompanyName: null,
+ isActive: false
+ });
+
+ return {
+ subscribe,
+ /**
+ * set the current resource context
+ * @param {string} type - resource type (e.g., 'campaign', 'template')
+ * @param {string|null} companyID - company id the resource belongs to (null for global)
+ * @param {string|null} companyName - company name (null for global)
+ */
+ setResource: (type, companyID, companyName) => {
+ set({
+ resourceType: type,
+ resourceCompanyID: companyID,
+ resourceCompanyName: companyName,
+ isActive: true
+ });
+ },
+ /**
+ * clear the resource context (call when leaving a resource page)
+ */
+ clear: () => {
+ set({
+ resourceType: null,
+ resourceCompanyID: null,
+ resourceCompanyName: null,
+ isActive: false
+ });
+ }
+ };
+}
+
+export const resourceContext = createResourceContextStore();
diff --git a/frontend/src/routes/campaign-template/+page.svelte b/frontend/src/routes/campaign-template/+page.svelte
index 371a590..e988d64 100644
--- a/frontend/src/routes/campaign-template/+page.svelte
+++ b/frontend/src/routes/campaign-template/+page.svelte
@@ -37,6 +37,7 @@
import CopyCell from '$lib/components/table/CopyCell.svelte';
import TextFieldSelectWithType from '$lib/components/form/TextFieldSelectWithType.svelte';
import ConditionalDisplay from '$lib/components/ConditionalDisplay.svelte';
+ import { resourceContext } from '$lib/store/resourceContext';
// services
const appStateService = AppStateService.instance;
@@ -135,8 +136,11 @@
}
hideIsLoading();
})();
+
+ // cleanup resource context when leaving page
return () => {
tableURLParams.unsubscribe();
+ resourceContext.clear();
};
});
@@ -403,14 +407,13 @@
const closeModal = () => {
isModalVisible = false;
- form?.reset();
formValues = {
id: null,
templateType: 'Email',
name: null,
domain: null,
landingPage: null,
- landingPageType: 'page',
+ landingPageType: 'page', // 'page' or 'proxy'
beforeLandingPage: null,
afterLandingPage: null,
afterLandingPageRedirectURL: '',
@@ -421,6 +424,10 @@
stateIdentifier: 'session',
urlPath: ''
};
+ modalError = '';
+ showAdvancedOptions = false;
+ // clear resource context when closing modal
+ resourceContext.clear();
};
/** @param {string} id */
@@ -432,10 +439,26 @@
const r = globalButtonDisabledAttributes(template, contextCompanyID);
if (r.disabled) {
hideIsLoading();
+ resourceContext.clear();
return;
}
assignTemplate(template);
+
+ // if company exists but name is missing, fetch it
+ if (template.companyID && !template.company?.name) {
+ try {
+ const companyRes = await api.company.getByID(template.companyID);
+ if (companyRes.success && companyRes.data) {
+ template.company = companyRes.data;
+ }
+ } catch (e) {
+ console.error('failed to load company details', e);
+ }
+ }
+
+ // set resource context for banner
+ resourceContext.setResource('template', template.companyID, template.company?.name);
isModalVisible = true;
} catch (e) {
addToast('Failed to load campaign template', 'Error');
@@ -452,6 +475,21 @@
const template = await getTemplate(id);
assignTemplate(template);
formValues.id = null;
+
+ // if company exists but name is missing, fetch it
+ if (template.companyID && !template.company?.name) {
+ try {
+ const companyRes = await api.company.getByID(template.companyID);
+ if (companyRes.success && companyRes.data) {
+ template.company = companyRes.data;
+ }
+ } catch (e) {
+ console.error('failed to load company details', e);
+ }
+ }
+
+ // set resource context for banner
+ resourceContext.setResource('template', template.companyID, template.company?.name);
isModalVisible = true;
} catch (e) {
addToast('Failed to load campaign template', 'Error');
@@ -513,8 +551,9 @@
+
- Campaign templates
+ Campaigns templates
New template
{
await refresh();
recipientTableUrlParams.onChange(refreshRecipients);
@@ -176,9 +180,12 @@
await refreshRecipientsTimes();
await refreshCampaignEventsSince();
})();
+
+ // cleanup resource context when leaving page
return () => {
recipientTableUrlParams.unsubscribe();
eventsTableURLParams.unsubscribe();
+ resourceContext.clear();
};
});
@@ -240,6 +247,21 @@
campaign.denyPage = t.denyPage;
campaign.evasionPage = t.evasionPage;
campaign.webhookID = t.webhookID;
+ campaign.companyID = t.companyID;
+ campaign.company = t.company;
+
+ // if company exists but name is missing, fetch it
+ if (campaign.companyID && !campaign.company?.name) {
+ try {
+ const companyRes = await api.company.getByID(campaign.companyID);
+ if (companyRes.success && companyRes.data) {
+ campaign.company = companyRes.data;
+ }
+ } catch (e) {
+ console.error('failed to load company details', e);
+ }
+ }
+
// fetch the full template object
if (t.templateID) {
const templateRes = await api.campaignTemplate.getByID(t.templateID, true);
@@ -255,6 +277,9 @@
if (campaign.allowDeny && campaign.allowDeny[0]) {
allowedFilter = campaign.allowDeny[0].allowed;
}
+
+ // set resource context for banner
+ resourceContext.setResource('campaign', campaign.companyID, campaign.company?.name);
} catch (e) {
addToast('Failed to load campaign', 'Error');
console.error('failed to load campaign', e);