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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/tabiya-tech/docs/refs/heads/main/.gitbook/assets/compass_logo_web_light.png#gh-dark-mode-only" alt="logo" width=300/>
<img src="https://raw.githubusercontent.com/tabiya-tech/docs/refs/heads/main/.gitbook/assets/compass_logo_web_dark.png#gh-light-mode-only" alt="logo" width=300/>
<img src="https://raw.githubusercontent.com/tabiya-tech/compass-connect/refs/heads/main/frontend-new/public/compass-connect-logo-light.svg#gh-dark-mode-only" alt="logo" width=400/>
<img src="https://raw.githubusercontent.com/tabiya-tech/compass-connect/refs/heads/main/frontend-new/public/compass-connect-logo-dark.svg#gh-light-mode-only" alt="logo" width=400/>
</h1>

**Compass Connect** is an AI chatbot designed to assist job-seekers in exploring and discovering their skills.
Expand Down
1 change: 1 addition & 0 deletions config/CUSTOMIZATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Only options exposed in these sections are customizable. Core application logic,
## App Name & Tab Title

- `branding.appName` — name displayed throughout the application
- `branding.country` — country name shown in dashboard (e.g. "opportunities in Zambia"); defaults to "your country" when unset
- `branding.browserTabTitle` — text shown in the browser tab

## SEO Metadata
Expand Down
1 change: 1 addition & 0 deletions config/default.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"branding": {
"appName": "Njila",
"country": "Zambia",
"browserTabTitle": "Njila",
"metaDescription": "Welcome to Njila! An AI-powered career assistant that helps jobseekers identify and showcase their skills. Join now to create a digital profile and connect with new opportunities.",
"seo": {
Expand Down
1 change: 1 addition & 0 deletions config/inject-config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

FRONTEND_ENV_MAP: Dict[str, str] = {
'GLOBAL_PRODUCT_NAME': 'branding.appName',
'GLOBAL_COUNTRY_NAME': 'branding.country',
'FRONTEND_BROWSER_TAB_TITLE': 'branding.browserTabTitle',
'FRONTEND_META_DESCRIPTION': 'branding.metaDescription',
'FRONTEND_LOGO_URL': 'branding.assets.logo',
Expand Down
1 change: 1 addition & 0 deletions config/njira.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"branding": {
"appName": "Njila",
"country": "Zambia",
"browserTabTitle": "Njila",
"metaDescription": "Welcome to Njila. An AI-powered career assistant that helps jobseekers identify and showcase their skills. Join now to create a digital profile and connect with new opportunities.",
"seo": {
Expand Down
1 change: 1 addition & 0 deletions frontend-new/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ To develop this application locally, follow these steps:
- `FRONTEND_DISABLE_REGISTRATION`: (**Optional**) A boolean value to disable the registration entirely.
- `FRONTEND_DISABLE_SOCIAL_AUTH`: (**Optional**) A boolean value to disable social authentication options on the login and registration pages.
- `FRONTEND_FEATURES`: (**optional**) A JSON like dictionary with the features enabled status and configurations specific to each feature.
- `FRONTEND_HIDE_PROGRAM_SKILLS`: (**Optional**) A boolean value to hide the program-skills section in 3 places: the home dashboard sidebar, the skills-discovery chat sidebar, and (as "Education Skills") the profile page. Shown by default; set to `true` to hide it in all three.
- `FRONTEND_SUPPORTED_LOCALES`:(**Mandatory**) A JSON array of enabled locale codes (e.g., ["en-GB", "en-US","es-ES","es-AR", "fr-FR"]). Refer to the constant [SupportedLocales](./src/i18n/constants.ts#SupportedLocales) for more about the supported locales. They must follow [IETF BCP 47](https://www.ietf.org/rfc/bcp/bcp47.txt) format.
- `FRONTEND_DEFAULT_LOCALE`:(**Mandatory**) Default UI language to use if the user preference is not set. It must be one of the supported locales.

Expand Down
6 changes: 6 additions & 0 deletions frontend-new/public/data/env.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ window.tabiyaConfig = {
// CV Upload feature flag (optional, defaults to false if not set)
GLOBAL_ENABLE_CV_UPLOAD: btoa("true"),

// Hides the program-skills section in 3 places: home dashboard sidebar, build your profile
// chat sidebar, and (as "Education Skills") the profile page. Shown by default; "true" to hide.
FRONTEND_HIDE_PROGRAM_SKILLS: btoa("false"),

// Optional features settings.
// ################################################################
// # Optional Features settings
Expand All @@ -95,6 +99,8 @@ window.tabiyaConfig = {
// # Branding Settings
// ################################################################
GLOBAL_PRODUCT_NAME: btoa("Compass"),
// Country name shown in user-facing dashboard copy (optional; defaults to "your country" if unset)
GLOBAL_COUNTRY_NAME: btoa("Zambia"),
FRONTEND_BROWSER_TAB_TITLE: btoa("Compass"),
FRONTEND_META_DESCRIPTION: btoa(
"Welcome to Compass! An AI-powered career assistant that helps jobseekers identify and showcase their skills."
Expand Down
2 changes: 2 additions & 0 deletions frontend-new/src/_test_utilities/envServiceMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jest.mock("src/envService", () => ({
getSupportedLocales: jest.fn(() => JSON.stringify(["en-US"])),
getDefaultLocale: jest.fn(() => "en-US"),
getProductName: jest.fn(() => "mockProduct"),
getCountryName: jest.fn(() => "your country"),
getBrowserTabTitle: jest.fn(() => "Mocked Browser Tab Title"),
getMetaDescription: jest.fn(() => "Mocked Meta Description"),
getSeoEnvVar: jest.fn(() => "{}"),
Expand All @@ -31,6 +32,7 @@ jest.mock("src/envService", () => ({
getGtmEnabled: jest.fn(() => "false"),
getChatAvatarUrl: jest.fn(() => ""),
getFaqTutorialVideoUrl: jest.fn(() => ""),
getProgramSkillsVisibility: jest.fn(() => true),
getIllustrationUrls: jest.fn(() => ({
loginHero: { src: "/climber.svg" },
loginFeature1: { src: "/conversation.svg" },
Expand Down
64 changes: 64 additions & 0 deletions frontend-new/src/envService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
getSkillsReportOutputConfigEnvVar,
getFaqTutorialVideoUrl,
getPartnerLogos,
getCountryName,
getProgramSkillsVisibility,
} from "./envService";
import { getRandomString } from "./_test_utilities/specialCharacters";

Expand Down Expand Up @@ -273,3 +275,65 @@ describe("Ensure Required Environment Variables", () => {
expect(console.warn).not.toHaveBeenCalled();
});
});

describe("GLOBAL_COUNTRY_NAME Getter (getCountryName) tests", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("should return the default 'your country' and warn when GLOBAL_COUNTRY_NAME is not set", () => {
// GIVEN the GLOBAL_COUNTRY_NAME environment variable is not set
Object.defineProperty(window, "tabiyaConfig", {
value: {},
writable: true,
});

// WHEN getCountryName is called
const actualCountryName = getCountryName();

// THEN expect it to return the generic default
expect(actualCountryName).toBe("your country");
// AND expect a warning to have been logged
expect(console.warn).toHaveBeenCalledWith("Country name not set, keeping the default");
});

test("should return the configured value when GLOBAL_COUNTRY_NAME is set", () => {
// GIVEN the GLOBAL_COUNTRY_NAME environment variable is set to a base64 encoded value
Object.defineProperty(window, "tabiyaConfig", {
value: {
GLOBAL_COUNTRY_NAME: btoa("Zambia"),
},
writable: true,
});

// WHEN getCountryName is called
const actualCountryName = getCountryName();

// THEN expect it to return the decoded value without warning
expect(actualCountryName).toBe("Zambia");
expect(console.warn).not.toHaveBeenCalled();
});
});

describe("FRONTEND_HIDE_PROGRAM_SKILLS Getter (getProgramSkillsVisibility) tests", () => {
test.each([
["unset", {}, true],
["empty", { FRONTEND_HIDE_PROGRAM_SKILLS: btoa("") }, true],
["'false'", { FRONTEND_HIDE_PROGRAM_SKILLS: btoa("false") }, true],
["'true'", { FRONTEND_HIDE_PROGRAM_SKILLS: btoa("true") }, false],
["'TRUE' (case-insensitive)", { FRONTEND_HIDE_PROGRAM_SKILLS: btoa("TRUE") }, false],
["a non-boolean value", { FRONTEND_HIDE_PROGRAM_SKILLS: btoa("something") }, true],
])("should resolve visibility correctly when the flag is %s", (_description, config, expectedVisibility) => {
// GIVEN the FRONTEND_HIDE_PROGRAM_SKILLS environment variable is in the given state
Object.defineProperty(window, "tabiyaConfig", {
value: config,
writable: true,
});

// WHEN getProgramSkillsVisibility is called
const actualVisibility = getProgramSkillsVisibility();

// THEN expect it to reflect whether the section should be shown
expect(actualVisibility).toBe(expectedVisibility);
});
});
15 changes: 15 additions & 0 deletions frontend-new/src/envService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export enum EnvVariables {
FRONTEND_GTM_ENABLED = "FRONTEND_GTM_ENABLED",
FRONTEND_FAQ_TUTORIAL_VIDEO_URL = "FRONTEND_FAQ_TUTORIAL_VIDEO_URL",
FRONTEND_ILLUSTRATIONS = "FRONTEND_ILLUSTRATIONS",
FRONTEND_HIDE_PROGRAM_SKILLS = "FRONTEND_HIDE_PROGRAM_SKILLS",
GLOBAL_COUNTRY_NAME = "GLOBAL_COUNTRY_NAME",
}

export const requiredEnvVariables = [
Expand Down Expand Up @@ -224,6 +226,16 @@ export const getProductName = () => {
return envAppName;
};

export const getCountryName = () => {
const envCountryName = getEnv(EnvVariables.GLOBAL_COUNTRY_NAME);
if (!envCountryName) {
console.warn("Country name not set, keeping the default");
return "your country";
}

return envCountryName;
};
Comment thread
Fidesnoella marked this conversation as resolved.

export const getBrowserTabTitle = () => getEnv(EnvVariables.FRONTEND_BROWSER_TAB_TITLE);

export const getMetaDescription = () => getEnv(EnvVariables.FRONTEND_META_DESCRIPTION);
Expand All @@ -238,6 +250,9 @@ export const getGtmEnabled = () => getEnv(EnvVariables.FRONTEND_GTM_ENABLED);

export const getFaqTutorialVideoUrl = () => getEnv(EnvVariables.FRONTEND_FAQ_TUTORIAL_VIDEO_URL);

export const getProgramSkillsVisibility = () =>
getEnv(EnvVariables.FRONTEND_HIDE_PROGRAM_SKILLS).toLowerCase() !== "true";

export const getLogoUrl = () => getEnv(EnvVariables.FRONTEND_LOGO_URL);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ exports[`Footer should render the footer container, logos, links, and collaborat
class="MuiTypography-root MuiTypography-body1 css-b00ug-MuiTypography-root"
data-testid="footer-collaboration-a7f3d2b1-8e4c-4a9f-b6d5-3c1e2f7a8b9d"
>
mockProduct is a free career guidance tool offered by the Zambia Ministry of Technology and Science, built in partnership with the World Bank Group and Tabiya.
mockProduct is a free career guidance tool offered by the {{country}} Ministry of Technology and Science, built in partnership with the World Bank Group and Tabiya.
</p>
</div>
</div>
Expand Down
33 changes: 19 additions & 14 deletions frontend-new/src/home/components/Sidebar/HomeSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Sidebar from "src/theme/Sidebar/Sidebar";
import { useWorkSkills } from "src/experiences/hooks/useWorkSkills";
import { useExperiencesDrawer } from "src/experiences/ExperiencesDrawerProvider";
import { useUserProfileContext } from "src/profile/UserProfileContext";
import { getProgramSkillsVisibility } from "src/envService";
import SectionTitle from "src/home/components/Sidebar/SectionTitle";
import ChipList from "src/home/components/Sidebar/ChipList";
import ViewCVCard from "src/home/components/Sidebar/ViewCVCard";
Expand Down Expand Up @@ -42,6 +43,8 @@ const HomeSidebar: React.FC<HomeSidebarProps> = ({ showViewCvButton = true }) =>
const quaternaryBg = theme.palette.quaternary.main;
const quaternaryText = theme.palette.quaternary.contrastText;

const isProgramSkillsVisible = getProgramSkillsVisibility();

const handleViewCV = () => void openExperiencesDrawer();

return (
Expand All @@ -60,20 +63,22 @@ const HomeSidebar: React.FC<HomeSidebarProps> = ({ showViewCvButton = true }) =>
expandButtonTestId={DATA_TEST_ID.HOME_SIDEBAR_SKILLS_FROM_WORK_EXPAND_BUTTON}
/>
</Box>
<Box>
<SectionTitle>{t("home.sidebar.home.skillsFromTEVET")}</SectionTitle>
<ChipList
chips={programmeSkills}
chipBgColor={quaternaryBg}
chipTextColor={quaternaryText}
chipBorderRadius="6px"
accentColor={quaternaryBg}
emptyText={t("home.sidebar.home.programmeSkillsEmpty")}
emptyTestId={DATA_TEST_ID.HOME_SIDEBAR_PROGRAMME_SKILLS_EMPTY}
chipTestId={DATA_TEST_ID.HOME_SIDEBAR_PROGRAMME_SKILLS_CHIP}
expandButtonTestId={DATA_TEST_ID.HOME_SIDEBAR_PROGRAMME_SKILLS_EXPAND_BUTTON}
/>
</Box>
{isProgramSkillsVisible && (
<Box>
<SectionTitle>{t("home.sidebar.home.skillsFromTEVET")}</SectionTitle>
<ChipList
chips={programmeSkills}
chipBgColor={quaternaryBg}
chipTextColor={quaternaryText}
chipBorderRadius="6px"
accentColor={quaternaryBg}
emptyText={t("home.sidebar.home.programmeSkillsEmpty")}
emptyTestId={DATA_TEST_ID.HOME_SIDEBAR_PROGRAMME_SKILLS_EMPTY}
chipTestId={DATA_TEST_ID.HOME_SIDEBAR_PROGRAMME_SKILLS_CHIP}
expandButtonTestId={DATA_TEST_ID.HOME_SIDEBAR_PROGRAMME_SKILLS_EXPAND_BUTTON}
/>
</Box>
)}
{showViewCvButton && (
<Box>
<SectionTitle>{t("home.sidebar.home.myExperience")}</SectionTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
import { Box, useTheme } from "@mui/material";
import { useTranslation } from "react-i18next";
import Sidebar from "src/theme/Sidebar/Sidebar";
import { getProgramSkillsVisibility } from "src/envService";
import SidebarService from "src/home/components/Sidebar/SidebarService";
import type { SkillsData } from "src/home/components/Sidebar/SidebarService";
import ChatProgressBar from "src/chat/chatProgressbar/ChatProgressBar";
Expand Down Expand Up @@ -84,10 +85,11 @@ const SkillsDiscoverySidebar: React.FC<SkillsDiscoverySidebarProps> = ({ current
};
}, [load, refreshToken]);

const isProgramSkillsVisible = getProgramSkillsVisibility();
const workSkills = data?.skills ?? [];
const hasMore = workSkills.length > COLLAPSE_AFTER;
const visibleWorkSkills = hasMore && !expanded ? workSkills.slice(0, COLLAPSE_AFTER) : workSkills;
const hasAnySkills = workSkills.length > 0 || programmeSkills.length > 0;
const hasAnySkills = workSkills.length > 0 || (isProgramSkillsVisible && programmeSkills.length > 0);

return (
<Sidebar title={t("home.sidebar.skillsDiscovery.title")} width="100%">
Expand Down Expand Up @@ -189,7 +191,7 @@ const SkillsDiscoverySidebar: React.FC<SkillsDiscoverySidebarProps> = ({ current
)}

{/* Programme skills */}
{programmeSkills.length > 0 && (
{isProgramSkillsVisible && programmeSkills.length > 0 && (
<Box>
<Box sx={subsectionLabelSx}>{t("home.sidebar.skillsDiscovery.fromTEVET")}</Box>
<Box sx={{ display: "flex", flexWrap: "wrap", gap: "6px" }}>
Expand Down
6 changes: 5 additions & 1 deletion frontend-new/src/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DEFAULT_LOCALE, FALL_BACK_LOCALE, Locale, SupportedLocales } from "./co
import { constructLocaleResources } from "./utils";
import { ConfigurationError } from "../error/commonErrors";
import { parseEnvSupportedLocales } from "./languageContextMenu/parseEnvSupportedLocales";
import { getProductName } from "src/envService";
import { getProductName, getCountryName } from "src/envService";

// --- Import translations ---
import enGb from "./locales/en-GB/translation.json";
Expand Down Expand Up @@ -62,6 +62,9 @@ const envSupportedLocales = parseEnvSupportedLocales();
// Get product name from environment variable
const productName = getProductName();

// Get the country name from the environment variable (defaults to a generic "your country")
const countryName = getCountryName();

i18n
.use(LanguageDetector)
.use(initReactI18next)
Expand Down Expand Up @@ -94,6 +97,7 @@ i18n
escapeValue: false,
defaultVariables: {
appName: productName,
country: countryName,
},
},
});
Expand Down
12 changes: 6 additions & 6 deletions frontend-new/src/i18n/locales/en-GB/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@
"feature1Title": "Discover your strengths",
"feature1Body": "Find out what skills you already have — from your training, your work, and your life.",
"feature2Title": "Build a CV that gets noticed",
"feature2Body": "Create a professional CV tailored to what employers in Zambia are looking for.",
"feature2Body": "Create a professional CV tailored to what employers in {{country}} are looking for.",
"feature3Title": "Find your path",
"feature3Body": "Explore in-demand careers, understand what qualifications you need, and match to real job openings."
},
Expand Down Expand Up @@ -978,7 +978,7 @@
"dashboard": "Your Career Hub",
"backToDashboard": "Back to dashboard",
"welcomeBack": "Welcome back, {{name}}",
"subtitle": "{{appName}} is the bridge between what you've learned and where you're headed. Chat with AI to surface your skills, prep for interviews, and explore real career routes across Zambia.",
"subtitle": "{{appName}} is the bridge between what you've learned and where you're headed. Chat with AI to surface your skills, prep for interviews, and explore real career routes across {{country}}.",
"profileStrength": "Your current profile strength is {{progress}}%.",
"profileStrengthHint": "That's a strong start — try My Skills & Interests to surface more about what you know and enjoy.",
"seeProfile": "See profile →",
Expand All @@ -987,7 +987,7 @@
"hero": {
"headline1": "Let's discover",
"headline2": "Your skills & strengths",
"body": "Your guided path from skills to your first job. Start by discovering what you bring to the table — then explore careers, build career readiness, and find real opportunities in Zambia.",
"body": "Your guided path from skills to your first job. Start by discovering what you bring to the table — then explore careers, build career readiness, and find real opportunities in {{country}}.",
"illustrationAlt": "Person at a signpost on a path, exploring career directions"
},
"cta": {
Expand All @@ -998,7 +998,7 @@
"explorePathsDesc": "Explore the sectors and careers you are interested in - salaries, qualifications pathways, training programs, top employers.",
"explorePathsCta": "Explore Career Paths",
"jobMatchesTitle": "Job Matches",
"jobMatchesDesc": "Real openings across Zambia, matched to what you can do right now.",
"jobMatchesDesc": "Real openings across {{country}}, matched to what you can do right now.",
"jobMatchesCta": "Browse your jobs"
},
"jobReadySection": {
Expand Down Expand Up @@ -1109,7 +1109,7 @@
"jobReadinessDesc": "Build job readiness through guided AI modules — from understanding your professional identity and CV writing to interviews, workplace readiness, and entrepreneurship.",
"careerExplorerDesc": "Explore career pathways in Energy, Mining, Agriculture and more — with salary data and qualification routes.",
"knowledgeHubDesc": "Explore sector profiles, salary benchmarks, and TEVET qualification pathways all in one place.",
"jobMatchingDesc": "See how your skills match to real job openings and explore labour market insights across Zambia.",
"jobMatchingDesc": "See how your skills match to real job openings and explore labour market insights across {{country}}.",
"continue": "Continue",
"completed": "Completed",
"soon": "Soon",
Expand All @@ -1120,7 +1120,7 @@
"termsOfUse": "Terms of Use",
"accessibility": "Accessibility",
"contact": "Contact",
"collaboration": "{{appName}} is a free career guidance tool offered by the Zambia Ministry of Technology and Science, built in partnership with the World Bank Group and Tabiya.",
"collaboration": "{{appName}} is a free career guidance tool offered by the {{country}} Ministry of Technology and Science, built in partnership with the World Bank Group and Tabiya.",
"compassConnectLine1": "This is a demo deployment of Compass Connect — a customizable, open-source career guidance tool built by <tabiyaLink>Tabiya</tabiyaLink>.",
"compassConnectLine2": "Built on <compassLink>Compass</compassLink>, Tabiya's open-source conversational tool for jobseekers.",
"worldBankLogoAlt": "World Bank Group Logo",
Expand Down
Loading
Loading