diff --git a/apps/customer-portal/microapp/.env.example b/apps/customer-portal/microapp/.env.example
new file mode 100644
index 000000000..1209baadb
--- /dev/null
+++ b/apps/customer-portal/microapp/.env.example
@@ -0,0 +1 @@
+VITE_BACKEND_URL=https://example.com/api
\ No newline at end of file
diff --git a/apps/customer-portal/microapp/.gitignore b/apps/customer-portal/microapp/.gitignore
new file mode 100644
index 000000000..45349c6b6
--- /dev/null
+++ b/apps/customer-portal/microapp/.gitignore
@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+package-lock.json
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# config.js
+/public/config.js
+
+.env
\ No newline at end of file
diff --git a/apps/customer-portal/microapp/.prettierrc b/apps/customer-portal/microapp/.prettierrc
new file mode 100644
index 000000000..6aa4deb48
--- /dev/null
+++ b/apps/customer-portal/microapp/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "endOfLine": "lf",
+ "singleQuote": false,
+ "trailingComma": "all",
+ "printWidth": 120,
+ "semi": true,
+ "tabWidth": 2
+}
diff --git a/apps/customer-portal/microapp/README.md b/apps/customer-portal/microapp/README.md
new file mode 100644
index 000000000..a78d9031d
--- /dev/null
+++ b/apps/customer-portal/microapp/README.md
@@ -0,0 +1,15 @@
+## Setting Up Environment Configuration
+
+1. **Create a file named `config.js` inside the `public` folder in root directory.**
+2. **Add the following code to `config.js`:**
+
+ ```javascript
+ window.config = {
+ ASGARDEO_BASE_URL: "", // Asgardeo base URL
+ CLIENT_ID: "", // Asgardeo client ID
+ SIGN_IN_REDIRECT_URL: "", // Redirect URL after sign in
+ SIGN_OUT_REDIRECT_URL: "", // Redirect URL after sign out
+ BACKEND_BASE_URL: "", // Backend API base URL
+ IS_MICROAPP: true,
+ };
+ ```
diff --git a/apps/customer-portal/microapp/eslint.config.js b/apps/customer-portal/microapp/eslint.config.js
new file mode 100644
index 000000000..0386a6708
--- /dev/null
+++ b/apps/customer-portal/microapp/eslint.config.js
@@ -0,0 +1,46 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import js from "@eslint/js";
+import globals from "globals";
+import reactHooks from "eslint-plugin-react-hooks";
+import reactRefresh from "eslint-plugin-react-refresh";
+import tseslint from "typescript-eslint";
+import { defineConfig, globalIgnores } from "eslint/config";
+import prettier from "eslint-plugin-prettier";
+import prettierConfig from "eslint-config-prettier";
+
+export default defineConfig([
+ globalIgnores(["dist"]),
+ {
+ files: ["**/*.{ts,tsx}"],
+ extends: [js.configs.recommended, ...tseslint.configs.recommended, prettierConfig],
+ plugins: {
+ "react-hooks": reactHooks,
+ "react-refresh": reactRefresh,
+ prettier: prettier,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ ...reactRefresh.configs.vite.rules,
+ "prettier/prettier": "error",
+ },
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+]);
diff --git a/apps/customer-portal/microapp/index.html b/apps/customer-portal/microapp/index.html
new file mode 100644
index 000000000..87032d99a
--- /dev/null
+++ b/apps/customer-portal/microapp/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+ Vite + React + TS
+
+
+
+
+
+
+
diff --git a/apps/customer-portal/microapp/package.json b/apps/customer-portal/microapp/package.json
new file mode 100644
index 000000000..c2f1b8413
--- /dev/null
+++ b/apps/customer-portal/microapp/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "microapp",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview",
+ "prettier": "prettier --write .",
+ "prettier:check": "prettier --check ."
+ },
+ "dependencies": {
+ "@asgardeo/auth-react": "^5.3.0",
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.1",
+ "@headlessui/react": "^2.2.3",
+ "@mui/icons-material": "^7.3.6",
+ "@mui/material": "^7.3.6",
+ "@tanstack/react-query": "^5.90.12",
+ "@types/react": "^19.1.3",
+ "@types/react-dom": "^19.1.3",
+ "@types/react-router-dom": "^5.3.3",
+ "axios": "^1.13.2",
+ "jwt-decode": "^4.0.0",
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-router-dom": "^7.9.1",
+ "vite-plugin-svgr": "^4.5.0",
+ "vite-tsconfig-paths": "^5.1.4",
+ "zustand": "^5.0.9"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@tanstack/eslint-plugin-query": "^5.91.2",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.3",
+ "eslint": "^9.39.1",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.4",
+ "eslint-plugin-react": "^7.37.5",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.5.0",
+ "prettier": "3.6.2",
+ "typescript": "~5.8.3",
+ "typescript-eslint": "^8.46.4",
+ "vite": "^7.1.7"
+ }
+}
diff --git a/apps/customer-portal/microapp/public/wso2-logo.svg b/apps/customer-portal/microapp/public/wso2-logo.svg
new file mode 100644
index 000000000..a85f74c35
--- /dev/null
+++ b/apps/customer-portal/microapp/public/wso2-logo.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/apps/customer-portal/microapp/src/App.css b/apps/customer-portal/microapp/src/App.css
new file mode 100644
index 000000000..df71b0b64
--- /dev/null
+++ b/apps/customer-portal/microapp/src/App.css
@@ -0,0 +1,56 @@
+/*
+Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+
+WSO2 LLC. licenses this file to you under the Apache License,
+Version 2.0 (the "License"); you may not use this file except
+in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+body {
+ margin: 0;
+ font-family: "Arial", sans-serif;
+ background-color: #fff;
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ background-color: #fff;
+ padding: 0 16px;
+}
+
+.loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+}
+
+.spinner {
+ border: 4px solid rgba(0, 0, 0, 0.1);
+ border-left-color: #d25d23;
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/apps/customer-portal/microapp/src/App.tsx b/apps/customer-portal/microapp/src/App.tsx
new file mode 100644
index 000000000..af3a6f54b
--- /dev/null
+++ b/apps/customer-portal/microapp/src/App.tsx
@@ -0,0 +1,31 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import React from "react";
+import HomePage from "@pages/HomePage";
+import { HashRouter as Router, Route, Routes } from "react-router-dom";
+
+const App: React.FC = () => {
+ return (
+
+
+ } />
+
+
+ );
+};
+
+export default App;
diff --git a/apps/customer-portal/microapp/src/components/microapp-bridge/index.ts b/apps/customer-portal/microapp/src/components/microapp-bridge/index.ts
new file mode 100644
index 000000000..ad1bab9c1
--- /dev/null
+++ b/apps/customer-portal/microapp/src/components/microapp-bridge/index.ts
@@ -0,0 +1,236 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { ErrorMessages } from "@utils/constants";
+import { Topic } from "./types";
+import type { LogLevel, TopicType } from "./types";
+
+type Callback = (data?: T) => void;
+
+// Bridge event topics used for communication between the main app and micro apps.
+const TOPIC = {
+ TOKEN: "token",
+ QR_REQUEST: "qr_request",
+ SAVE_LOCAL_DATA: "save_local_data",
+ GET_LOCAL_DATA: "get_local_data",
+ ALERT: "alert",
+ CONFIRM_ALERT: "confirm_alert",
+ TOTP: "totp",
+};
+
+declare global {
+ interface Window {
+ nativebridge?: {
+ requestToken: () => void;
+ resolveToken: (token: string) => void;
+ requestQR: () => void;
+ resolveQR: (qrString: string) => void;
+ requestItemList: () => void;
+ resolveConfirmAlert: (action: string) => void;
+ resolveQRCode: (qrData: string) => void;
+ rejectQRCode: (error: string) => void;
+ resolveSaveLocalData: () => void;
+ rejectSaveLocalData: (error: string) => void;
+ resolveGetLocalData: (encodedData: { value?: string }) => void;
+ rejectGetLocalData: (error: string) => void;
+ resolveTotpQrMigrationData: (encodedData: { data: string }) => void;
+ rejectTotpQrMigrationData: (error: string) => void;
+ };
+ ReactNativeWebView?: {
+ postMessage: (message: string) => void;
+ };
+ }
+}
+
+// Function to get token from React Native
+export const getToken = (callback: Callback): void => {
+ if (window.nativebridge) {
+ window.nativebridge.requestToken();
+ window.nativebridge.resolveToken = (token: string) => {
+ callback(token);
+ };
+ } else {
+ console.error("Native bridge is not available");
+ callback();
+ }
+};
+
+// Function to show alert in React Native
+export const showAlert = (title: string, message: string, buttonText: string): void => {
+ if (window.nativebridge && window.ReactNativeWebView) {
+ const alertData = JSON.stringify({
+ topic: TOPIC.ALERT,
+ data: { title, message, buttonText },
+ });
+
+ window.ReactNativeWebView.postMessage(alertData);
+ } else {
+ console.error("Native bridge is not available");
+ }
+};
+
+// Function to show confirm alert in React Native
+export const showConfirmAlert = (
+ title: string,
+ message: string,
+ confirmButtonText: string,
+ cancelButtonText: string,
+ confirmCallback: () => void,
+ cancelCallback: () => void,
+): void => {
+ if (window.nativebridge && window.ReactNativeWebView) {
+ const confirmData = JSON.stringify({
+ topic: TOPIC.CONFIRM_ALERT,
+ data: { title, message, confirmButtonText, cancelButtonText },
+ });
+
+ window.ReactNativeWebView.postMessage(confirmData);
+
+ // Handling response from React Native side
+ window.nativebridge.resolveConfirmAlert = (action: string) => {
+ if (action === "confirm") {
+ confirmCallback();
+ } else if (action === "cancel") {
+ cancelCallback();
+ }
+ };
+ } else {
+ console.error("Native bridge is not available");
+ }
+};
+
+// Scan QR Code
+export const scanQRCode = (
+ successCallback: (qrData: string) => void,
+ failedToRespondCallback: (error: string) => void,
+): void => {
+ if (window.nativebridge && window.ReactNativeWebView) {
+ window.ReactNativeWebView.postMessage(JSON.stringify({ topic: TOPIC.QR_REQUEST }));
+
+ window.nativebridge.resolveQRCode = (qrData: string) => successCallback(qrData);
+ window.nativebridge.rejectQRCode = (error: string) => failedToRespondCallback(error);
+ } else {
+ console.error("Native bridge is not available");
+ }
+};
+
+// Save Local Data
+export const saveLocalData = (
+ key: string,
+ value: unknown,
+ callback: () => void,
+ failedToRespondCallback: (error: string) => void,
+): void => {
+ key = key.toString().replace(" ", "-").toLowerCase();
+ const encodedValue = btoa(JSON.stringify(value));
+
+ if (window.nativebridge && window.ReactNativeWebView) {
+ window.ReactNativeWebView.postMessage(
+ JSON.stringify({
+ topic: TOPIC.SAVE_LOCAL_DATA,
+ data: { key, value: encodedValue },
+ }),
+ );
+
+ window.nativebridge.resolveSaveLocalData = callback;
+ window.nativebridge.rejectSaveLocalData = (error: string) => failedToRespondCallback(error);
+ } else {
+ console.error("Native bridge is not available");
+ }
+};
+
+// Get Local Data
+export const getLocalData = (
+ key: string,
+ callback: (data: T | null) => void,
+ failedToRespondCallback: (error: string) => void,
+): void => {
+ key = key.toString().replace(" ", "-").toLowerCase();
+
+ if (window.nativebridge && window.ReactNativeWebView) {
+ window.ReactNativeWebView.postMessage(JSON.stringify({ topic: TOPIC.GET_LOCAL_DATA, data: { key } }));
+
+ window.nativebridge.resolveGetLocalData = (encodedData: { value?: string }) => {
+ if (!encodedData.value) {
+ callback(null);
+ } else {
+ callback(JSON.parse(atob(encodedData.value)) as T);
+ }
+ };
+
+ window.nativebridge.rejectGetLocalData = (error: string) => failedToRespondCallback(error);
+ } else {
+ console.error("Native bridge is not available");
+ }
+};
+
+// TOTP QR Migration Data
+export const totpQrMigrationData = (
+ callback: (data: string[]) => void,
+ failedToRespondCallback: (error: string) => void,
+): void => {
+ if (window.nativebridge && window.ReactNativeWebView) {
+ window.ReactNativeWebView.postMessage(JSON.stringify({ topic: TOPIC.TOTP }));
+
+ window.nativebridge.resolveTotpQrMigrationData = (encodedData: { data: string }) => {
+ if (encodedData.data) {
+ callback(encodedData.data.replace(" ", "").split(","));
+ } else {
+ callback([]);
+ }
+ };
+
+ window.nativebridge.rejectTotpQrMigrationData = (error: string) => failedToRespondCallback(error);
+ } else {
+ console.error("Native bridge is not available");
+ }
+};
+
+/**
+ * Trigger an action in the super app
+ * @param topic - The topic to trigger
+ * @param data - The data to send
+ */
+const triggerSuperAppAction = (topic: TopicType, data?: unknown): void => {
+ if (window.ReactNativeWebView) {
+ const messageData = JSON.stringify({
+ topic,
+ data,
+ });
+ window.ReactNativeWebView.postMessage(messageData);
+ } else {
+ console.error(ErrorMessages.NATIVE_BRIDGE_NOT_AVAILABLE);
+ }
+};
+
+/**
+ * Send a log message to the native side
+ * @param message - The message to send
+ * @param data - The data to send
+ * @param level - The level of the log
+ */
+export const sendNativeLog = (message?: string, data?: unknown, level: LogLevel = "debug"): void => {
+ if (window.nativebridge && window.ReactNativeWebView) {
+ triggerSuperAppAction(Topic.nativeLog, {
+ message,
+ data,
+ level,
+ });
+ } else {
+ // TODO: Replace this with Logger.error(ErrorMessages.NATIVE_BRIDGE_NOT_AVAILABLE)
+ console.error(ErrorMessages.NATIVE_BRIDGE_NOT_AVAILABLE);
+ }
+};
diff --git a/apps/customer-portal/microapp/src/components/microapp-bridge/types.ts b/apps/customer-portal/microapp/src/components/microapp-bridge/types.ts
new file mode 100644
index 000000000..58baebdba
--- /dev/null
+++ b/apps/customer-portal/microapp/src/components/microapp-bridge/types.ts
@@ -0,0 +1,34 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+export const Topic = {
+ token: "token",
+ nativeLog: "native_log",
+ navigateToMyApps: "close_webview",
+ saveLocalData: "save_local_data",
+ getLocalData: "get_local_data",
+ deviceSafeAreaInsets: "device_safe_area_insets",
+ deleteLocalData: "delete_local_data",
+ openUrl: "open_url",
+ scheduleLocalNotification: "scheduling_local_notification",
+ cancelLocalNotification: "cancelling_local_notification",
+ clearAllLocalNotifications: "clearing_all_local_notifications",
+ qrRequest: "qr_request",
+} as const;
+
+export type TopicType = (typeof Topic)[keyof typeof Topic];
+
+export type LogLevel = "error" | "warn" | "info" | "debug";
diff --git a/apps/customer-portal/microapp/src/config/config.ts b/apps/customer-portal/microapp/src/config/config.ts
new file mode 100644
index 000000000..7e3aacfc4
--- /dev/null
+++ b/apps/customer-portal/microapp/src/config/config.ts
@@ -0,0 +1,43 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+declare global {
+ interface Window {
+ config: {
+ CLIENT_ID: string;
+ SIGN_IN_REDIRECT_URL: string;
+ SIGN_OUT_REDIRECT_URL: string;
+ ASGARDEO_BASE_URL: string;
+ IS_MICROAPP: boolean;
+ BACKEND_BASE_URL: string;
+ };
+ }
+}
+
+export const CLIENT_ID = window.config.CLIENT_ID;
+export const SIGN_IN_REDIRECT_URL = window.config.SIGN_IN_REDIRECT_URL;
+export const SIGN_OUT_REDIRECT_URL = window.config.SIGN_OUT_REDIRECT_URL;
+export const ASGARDEO_BASE_URL = window.config.ASGARDEO_BASE_URL;
+export const IS_MICROAPP = window.config.IS_MICROAPP;
+
+// TODO: Uncomment and update the `baseUrl` variable when the backend URL configuration is available and in use.
+// const baseUrl = window.config.BACKEND_BASE_URL;
+
+export const serviceUrls = {
+ serviceUrls: {
+ // TODO: Add service URLs here as needed for future implementation.
+ },
+};
diff --git a/apps/customer-portal/microapp/src/config/constants.ts b/apps/customer-portal/microapp/src/config/constants.ts
new file mode 100644
index 000000000..6a07eca6d
--- /dev/null
+++ b/apps/customer-portal/microapp/src/config/constants.ts
@@ -0,0 +1,51 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import {
+ AutorenewOutlined,
+ Bedtime,
+ CalendarMonthOutlined,
+ ChatBubbleOutline,
+ Cloud,
+ ErrorOutline,
+ PeopleAltOutlined,
+ Report,
+ SettingsOutlined,
+ ThumbUpAlt,
+ type SvgIconComponent,
+} from "@mui/icons-material";
+import type { ProjectMetricKey, ProjectMetricMeta, ProjectStatus, ProjectType } from "@features/projects";
+
+export const INPUT_INVALID_MSG_GATEWAY = "INPUT_INVALID_MSG_GATEWAY";
+
+export const PROJECT_METRIC_META: Record = {
+ cases: { label: "Cases:", color: "semantic.portal.accent.orange", icon: ErrorOutline },
+ chats: { label: "Chats:", color: "semantic.portal.accent.blue", icon: ChatBubbleOutline },
+ service: { label: "Service:", color: "semantic.portal.accent.purple", icon: SettingsOutlined },
+ change: { label: "Change:", color: "semantic.portal.accent.cyan", icon: AutorenewOutlined },
+ users: { label: "Users:", color: "text.primary", icon: PeopleAltOutlined },
+ date: { label: "Date:", icon: CalendarMonthOutlined },
+};
+
+export const PROJECT_TYPE_META: Record = {
+ Regular: { icon: Bedtime },
+ "Managed Cloud": { icon: Cloud },
+};
+
+export const PROJECT_STATUS_META: Record = {
+ "All Good": { color: "success", icon: ThumbUpAlt },
+ "Needs Attention": { color: "warning", icon: Report },
+};
diff --git a/apps/customer-portal/microapp/src/config/endpoints.ts b/apps/customer-portal/microapp/src/config/endpoints.ts
new file mode 100644
index 000000000..e8c023cbc
--- /dev/null
+++ b/apps/customer-portal/microapp/src/config/endpoints.ts
@@ -0,0 +1,23 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL;
+
+if (!BACKEND_URL) {
+ throw new Error("VITE_BACKEND_URL is not defined");
+}
+
+export const PROJECTS_ENDPOINT = "/x_wso2_customer_0/projects";
diff --git a/apps/customer-portal/microapp/src/features/projects/ProjectCard.tsx b/apps/customer-portal/microapp/src/features/projects/ProjectCard.tsx
new file mode 100644
index 000000000..665a250c3
--- /dev/null
+++ b/apps/customer-portal/microapp/src/features/projects/ProjectCard.tsx
@@ -0,0 +1,104 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { ArrowForward, type SvgIconComponent } from "@mui/icons-material";
+import { Box, ButtonBase as Button, Card, Chip, Grid, Stack, Typography } from "@mui/material";
+import { PROJECT_METRIC_META, PROJECT_STATUS_META, PROJECT_TYPE_META } from "@root/src/config/constants";
+
+export type ProjectStatus = "All Good" | "Needs Attention";
+export type ProjectType = "Managed Cloud" | "Regular";
+export type ProjectMetricKey = "cases" | "chats" | "service" | "change" | "users" | "date";
+export type ProjectMetricValue = number | string;
+export type ProjectMetrics = Partial>;
+
+export interface ProjectMetricMeta {
+ label: string;
+ icon: SvgIconComponent;
+ color?: string;
+}
+
+export interface ProjectCardProps {
+ id: string;
+ name: string;
+ description: string;
+ type: ProjectType;
+ status: ProjectStatus;
+ metrics: ProjectMetrics;
+}
+
+export function ProjectCard({ id, name, description, type, status, metrics }: ProjectCardProps) {
+ const TypeChipIcon = PROJECT_TYPE_META[type].icon;
+ const StatusChipIcon = PROJECT_STATUS_META[status].icon;
+ const statusChipColorVariant = PROJECT_STATUS_META[status].color;
+
+ return (
+ ({ borderRadius: 3, border: `1px solid ${theme.palette.divider}` })}>
+
+
+
+ {id}
+
+ }
+ iconPosition="end"
+ />
+
+
+ {name}
+
+ } sx={{ alignSelf: "start", borderRadius: 1 }} />
+ {description}
+
+
+ {Object.keys(metrics).map((key) => {
+ const meta = PROJECT_METRIC_META[key as ProjectMetricKey];
+ const value = metrics[key as ProjectMetricKey];
+
+ if (value === undefined) return null;
+
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+
+ );
+}
+
+function MetricItem({ meta, value }: { meta: ProjectMetricMeta; value: ProjectMetricValue }) {
+ return (
+
+ ({ color: "text.secondary", fontSize: theme.typography.pxToRem(20) })} />
+
+ {meta.label}
+
+
+ {value}
+
+
+ );
+}
diff --git a/apps/customer-portal/microapp/src/features/projects/index.ts b/apps/customer-portal/microapp/src/features/projects/index.ts
new file mode 100644
index 000000000..b0ecb3aa4
--- /dev/null
+++ b/apps/customer-portal/microapp/src/features/projects/index.ts
@@ -0,0 +1,17 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+export * from "./ProjectCard";
diff --git a/apps/customer-portal/microapp/src/icons/.gitkeep b/apps/customer-portal/microapp/src/icons/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/customer-portal/microapp/src/index.css b/apps/customer-portal/microapp/src/index.css
new file mode 100644
index 000000000..b70b3de2d
--- /dev/null
+++ b/apps/customer-portal/microapp/src/index.css
@@ -0,0 +1,28 @@
+/*
+Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+
+WSO2 LLC. licenses this file to you under the Apache License,
+Version 2.0 (the "License"); you may not use this file except
+in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap");
+
+* {
+ font-family: "Plus Jakarta Sans", sans-serif;
+ font-optical-sizing: auto;
+}
+
+body {
+ margin: 0;
+}
diff --git a/apps/customer-portal/microapp/src/main.tsx b/apps/customer-portal/microapp/src/main.tsx
new file mode 100644
index 000000000..c40a8919d
--- /dev/null
+++ b/apps/customer-portal/microapp/src/main.tsx
@@ -0,0 +1,48 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "@src/index.css";
+import App from "@src/App";
+import { AuthProvider } from "@asgardeo/auth-react";
+import { ASGARDEO_BASE_URL, CLIENT_ID, SIGN_IN_REDIRECT_URL, SIGN_OUT_REDIRECT_URL } from "@config/config";
+import { CssBaseline, ThemeProvider } from "@mui/material";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import theme from "@src/theme";
+
+const authConfig = {
+ clientID: CLIENT_ID || "",
+ baseUrl: ASGARDEO_BASE_URL || "",
+ signInRedirectURL: SIGN_IN_REDIRECT_URL || "",
+ signOutRedirectURL: SIGN_OUT_REDIRECT_URL || "",
+ scope: ["openid", "profile", "email"],
+};
+
+const queryClient = new QueryClient();
+
+createRoot(document.getElementById("root")!).render(
+
+
+
+
+
+
+
+
+
+ ,
+);
diff --git a/apps/customer-portal/microapp/src/pages/HomePage.tsx b/apps/customer-portal/microapp/src/pages/HomePage.tsx
new file mode 100644
index 000000000..0b2d14404
--- /dev/null
+++ b/apps/customer-portal/microapp/src/pages/HomePage.tsx
@@ -0,0 +1,75 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { Box, CircularProgress, Stack, Typography } from "@mui/material";
+import { FolderOpen } from "@mui/icons-material";
+import { ProjectCard } from "@features/projects";
+import { useSuspenseQuery } from "@tanstack/react-query";
+import { getProjects } from "@src/services/projects";
+import { Suspense } from "react";
+
+export default function HomePage() {
+ return (
+
+
+
+ }
+ >
+
+
+ );
+}
+
+function HomeContent() {
+ const { data } = useSuspenseQuery({
+ queryKey: ["projects"],
+ queryFn: getProjects,
+ });
+
+ return (
+
+
+
+
+ Select Your Project
+
+
+
+ Choose a project to access your support cases, chat history, and dashboard
+
+
+ {data.map((project) => (
+
+ ))}
+
+
+
+ Need access to another project? Contact your administrator
+
+
+
+ );
+}
diff --git a/apps/customer-portal/microapp/src/services/apiClient.ts b/apps/customer-portal/microapp/src/services/apiClient.ts
new file mode 100644
index 000000000..ebd88242f
--- /dev/null
+++ b/apps/customer-portal/microapp/src/services/apiClient.ts
@@ -0,0 +1,166 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import axios, { type InternalAxiosRequestConfig } from "axios";
+import { Logger } from "@utils/logger";
+import { refreshToken } from "./auth";
+import { BACKEND_URL } from "@config/endpoints";
+
+// Variables/constants
+let isRefreshing = false;
+let failedQueue: {
+ resolve: (value: unknown) => void;
+ reject: (reason?: unknown) => void;
+}[] = [];
+
+// Holds the refresh token promise
+let refreshTokenPromise: Promise | null = null;
+
+// axios instance
+const apiClient = axios.create({
+ baseURL: BACKEND_URL,
+});
+
+/**
+ * Request Interceptor
+ */
+apiClient.interceptors.request.use(
+ async (config: InternalAxiosRequestConfig) => {
+ // Log the outgoing request
+ Logger.info(`Making ${config.method?.toUpperCase()} request to: ${config.baseURL || ""}${config.url || ""}`, {
+ url: config.url,
+ method: config.method,
+ baseURL: config.baseURL,
+ fullURL: `${config.baseURL || ""}${config.url || ""}`,
+ headers: config.headers,
+ });
+
+ // Use a singleton promise for token refresh
+ if (!refreshTokenPromise) {
+ refreshTokenPromise = refreshToken().finally(() => {
+ // Reset the promise once it's resolved or rejected
+ refreshTokenPromise = null;
+ });
+ }
+
+ try {
+ const token = ""; // TODO: Replace with `const token = await refreshTokenPromise;`
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ config.headers["Content-Type"] = "application/json";
+ Logger.info("Added authorization token to request");
+ } else {
+ Logger.warn("No token available for request");
+ }
+ } catch (error) {
+ Logger.error("Failed to get token for request", error);
+ return Promise.reject(error);
+ }
+
+ return config;
+ },
+ (error) => {
+ Logger.error("Request interceptor error", error);
+ return Promise.reject(error);
+ },
+);
+
+/**
+ * Response Interceptor
+ */
+const processQueue = (error: unknown, token: string | null = null) => {
+ failedQueue.forEach((prom) => {
+ if (error) {
+ prom.reject(error);
+ } else {
+ prom.resolve(token);
+ }
+ });
+
+ failedQueue = [];
+};
+
+apiClient.interceptors.response.use(
+ (response) => {
+ // Any status code within the range of 2xx causes this function to trigger
+ Logger.info(`Successful response from ${response.config.method?.toUpperCase()} ${response.config.url}`, {
+ status: response.status,
+ statusText: response.statusText,
+ url: response.config.url,
+ data: {
+ ...response.data,
+ dataSize: response.data ? JSON.stringify(response.data).length : 0,
+ },
+ });
+ return response;
+ },
+ async (error) => {
+ if (error.code === axios.isCancel(error)) {
+ return Promise.reject(error);
+ }
+ Logger.error(`API request failed`, {
+ status: error.response?.status,
+ statusText: error.response?.statusText,
+ url: error.config?.url,
+ method: error.config?.method,
+ message: error.message,
+ });
+
+ const originalRequest = error.config;
+
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ Logger.warn("Received 401 unauthorized, attempting token refresh");
+
+ if (isRefreshing) {
+ Logger.info("Token refresh already in progress, queuing request");
+ return new Promise((resolve, reject) => {
+ failedQueue.push({ resolve, reject });
+ })
+ .then((token) => {
+ originalRequest.headers["Authorization"] = "Bearer " + token;
+ return apiClient(originalRequest);
+ })
+ .catch((err) => {
+ return Promise.reject(err);
+ });
+ }
+
+ originalRequest._retry = true;
+ isRefreshing = true;
+
+ try {
+ Logger.info("Attempting to refresh access token");
+ const newAccessToken = await refreshToken();
+ apiClient.defaults.headers.common["Authorization"] = `Bearer ${newAccessToken}`;
+ originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`;
+ processQueue(null, newAccessToken);
+ Logger.info("Token refresh successful, retrying original request");
+ return apiClient(originalRequest);
+ } catch (refreshError) {
+ Logger.error("Token refresh failed", refreshError);
+ processQueue(refreshError, null);
+ console.error("Token refresh failed:", refreshError);
+ return Promise.reject(refreshError);
+ } finally {
+ isRefreshing = false;
+ }
+ }
+
+ return Promise.reject(error);
+ },
+);
+
+export default apiClient;
diff --git a/apps/customer-portal/microapp/src/services/auth.ts b/apps/customer-portal/microapp/src/services/auth.ts
new file mode 100644
index 000000000..96d535b98
--- /dev/null
+++ b/apps/customer-portal/microapp/src/services/auth.ts
@@ -0,0 +1,138 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { jwtDecode } from "jwt-decode";
+import { getToken } from "@components/microapp-bridge";
+import { LocalStorageKeys } from "@utils/constants";
+import { Logger } from "@utils/logger";
+import { useUserStore, type User } from "../store/user";
+
+// Token Payload
+interface TokenPayload {
+ email?: string;
+ name?: string;
+ groups?: string[];
+ given_name: string;
+ family_name: string;
+}
+
+// These would be your actual token storage functions
+export const getAccessToken = (): string | null => localStorage.getItem(LocalStorageKeys.accessToken);
+export const setAccessToken = (token: string): void => localStorage.setItem(LocalStorageKeys.accessToken, token);
+export const getIdToken = (): string | null => localStorage.getItem(LocalStorageKeys.idToken);
+export const setIdToken = (token: string): void => localStorage.setItem(LocalStorageKeys.idToken, token);
+
+/**
+ * A function to refresh the token.
+ * This is a simplified version of the logic in the original `handleRequestWithNewToken`.
+ * It fetches a new token and updates it in storage.
+ */
+export const refreshToken = (): Promise => {
+ return new Promise((resolve, reject) => {
+ getToken((newIdToken: string | undefined) => {
+ if (newIdToken) {
+ setIdToken(newIdToken);
+ setAccessToken(newIdToken);
+
+ // Automatically decode and store user information when token is refreshed
+ try {
+ initializeUserFromToken();
+ Logger.info("User information updated after token refresh");
+ } catch (error) {
+ Logger.warn("Failed to update user information after token refresh", error);
+ }
+
+ resolve(newIdToken);
+ } else {
+ Logger.error("Failed to refresh token");
+ reject("Failed to refresh token");
+ }
+ });
+ });
+};
+
+/**
+ * Checks if the user belongs to a given group or groups based on the ID token.
+ * This is a direct replacement for `handleCheckGroups`.
+ * @param groupNames - A single group name or an array of group names.
+ * @returns boolean - True if the user is in at least one of the required groups.
+ */
+export const checkUserGroups = (groupNames: string | string[]): boolean => {
+ const token = getIdToken();
+
+ if (!token) {
+ Logger.error("ID token not found for group check.");
+ return false;
+ }
+
+ const decoded = jwtDecode(token);
+ const userGroups = decoded.groups ?? [];
+ const requiredGroups = Array.isArray(groupNames) ? groupNames : [groupNames];
+
+ return requiredGroups.some((group) => userGroups.includes(group));
+};
+
+/**
+ * Decodes the ID token and extracts user information.
+ * Stores the user information in the Zustand user store.
+ * @returns User object or null if token is invalid
+ */
+export const decodeTokenAndStoreUser = (): User | null => {
+ try {
+ const token = getIdToken();
+
+ if (!token) {
+ Logger.error("ID token not found for user decoding.");
+ useUserStore.getState().clearUser();
+ return null;
+ }
+
+ const decoded = jwtDecode(token);
+ Logger.info("Token decoded successfully", {
+ email: decoded.email,
+ name: decoded.name,
+ });
+
+ // Extract user information from token
+ const user: User = {
+ email: decoded.email || "",
+ name: `${decoded.given_name || ""} ${decoded.family_name || ""}`,
+ };
+
+ return user;
+ } catch (error) {
+ Logger.error("Failed to decode token and store user information", error);
+ useUserStore.getState().clearUser();
+ return null;
+ }
+};
+
+/**
+ * Initializes user data from stored token.
+ * Should be called when the app starts or when a new token is received.
+ */
+export const initializeUserFromToken = (): void => {
+ useUserStore.getState().setLoading(true);
+
+ try {
+ decodeTokenAndStoreUser();
+ } catch (error) {
+ Logger.error("Failed to initialize user from token", error);
+ useUserStore.getState().clearUser();
+ } finally {
+ useUserStore.getState().setLoading(false);
+ }
+};
diff --git a/apps/customer-portal/microapp/src/services/projects.ts b/apps/customer-portal/microapp/src/services/projects.ts
new file mode 100644
index 000000000..68ccb5240
--- /dev/null
+++ b/apps/customer-portal/microapp/src/services/projects.ts
@@ -0,0 +1,58 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import apiClient from "@src/services/apiClient";
+import type { ProjectCardProps } from "@features/projects";
+import { PROJECTS_ENDPOINT } from "@config/endpoints";
+
+export interface ProjectsResponseType {
+ projects: [
+ {
+ sysId: string;
+ name: string;
+ description: string;
+ projectKey: string;
+ createdOn: string;
+ activeChatsCount: number;
+ openCasesCount: number;
+ },
+ ];
+ pagination: { offset: number; limit: number; totalRecords: number };
+}
+
+export const getProjects = async (): Promise => {
+ const response = await apiClient.get(PROJECTS_ENDPOINT);
+
+ return response.data.projects.map((project) => ({
+ id: project.projectKey,
+ name: project.name,
+ description: project.description,
+
+ // TODO: determine project type from backend
+ // Fallback to "Managed Cloud" until backend provides explicit field
+ type: "Managed Cloud",
+
+ // TODO: determine project status from backend
+ // Fallback to "All Good" until backend provides explicit field
+ status: "All Good",
+
+ metrics: {
+ cases: project.openCasesCount,
+ chats: project.activeChatsCount,
+ // TODO: populate remaining metrics when supported by backend
+ },
+ }));
+};
diff --git a/apps/customer-portal/microapp/src/store/user.ts b/apps/customer-portal/microapp/src/store/user.ts
new file mode 100644
index 000000000..f06c9fef8
--- /dev/null
+++ b/apps/customer-portal/microapp/src/store/user.ts
@@ -0,0 +1,50 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { create } from "zustand";
+
+export interface User {
+ email: string;
+ name: string;
+}
+
+export interface UserStore {
+ user: User | null;
+ isLoading: boolean;
+
+ setUser: (user: User) => void;
+ clearUser: () => void;
+ setLoading: (loading: boolean) => void;
+}
+
+export const useUserStore = create((set) => ({
+ user: null,
+ isLoading: false,
+
+ setUser: (user: User) =>
+ set({
+ user,
+ isLoading: false,
+ }),
+
+ clearUser: () =>
+ set({
+ user: null,
+ isLoading: false,
+ }),
+
+ setLoading: (loading: boolean) => set({ isLoading: loading }),
+}));
diff --git a/apps/customer-portal/microapp/src/theme/index.ts b/apps/customer-portal/microapp/src/theme/index.ts
new file mode 100644
index 000000000..142dc51ac
--- /dev/null
+++ b/apps/customer-portal/microapp/src/theme/index.ts
@@ -0,0 +1,29 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { createTheme } from "@mui/material";
+import { palette } from "@theme/palette";
+import { typography } from "@theme/typography";
+import ComponentOverrides from "@theme/overrides";
+
+const theme = createTheme({
+ palette,
+ typography,
+});
+
+theme.components = ComponentOverrides(theme);
+
+export default theme;
diff --git a/apps/customer-portal/microapp/src/theme/overrides/buttons.ts b/apps/customer-portal/microapp/src/theme/overrides/buttons.ts
new file mode 100644
index 000000000..4ed738580
--- /dev/null
+++ b/apps/customer-portal/microapp/src/theme/overrides/buttons.ts
@@ -0,0 +1,58 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import type { Theme, Components } from "@mui/material";
+
+export default function Buttons(theme: Theme): Components {
+ return {
+ MuiButtonBase: {
+ styleOverrides: {
+ root: {
+ padding: 10,
+ borderRadius: 8,
+ fontSize: theme.typography.button.fontSize,
+ display: "flex",
+ gap: 7,
+ variants: [
+ {
+ props: { variant: "outlined" },
+ style: {
+ background: theme.palette.background.default,
+ color: theme.palette.text.primary,
+ border: `1px solid ${theme.palette.semantic.border.subtle}`,
+ },
+ },
+ {
+ props: { variant: "contained" },
+ style: {
+ color: theme.palette.primary.contrastText,
+ background: theme.palette.primary.main,
+ borderColor: theme.palette.primary.main,
+ borderWidth: 1,
+ },
+ },
+ ],
+ },
+ },
+ },
+ };
+}
+
+declare module "@mui/material/ButtonBase" {
+ interface ButtonBaseOwnProps {
+ variant?: "outlined" | "contained";
+ }
+}
diff --git a/apps/customer-portal/microapp/src/theme/overrides/chips.ts b/apps/customer-portal/microapp/src/theme/overrides/chips.ts
new file mode 100644
index 000000000..5dfe5b0fa
--- /dev/null
+++ b/apps/customer-portal/microapp/src/theme/overrides/chips.ts
@@ -0,0 +1,65 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import type { Theme, Components, ChipOwnProps } from "@mui/material";
+
+export default function Chips(theme: Theme): Components {
+ return {
+ MuiChip: {
+ styleOverrides: {
+ root: ({ ownerState }: { ownerState?: ChipOwnProps }) => {
+ const iconPosition = ownerState?.iconPosition ?? "start";
+
+ return {
+ display: "flex",
+ gap: 5,
+
+ ...(iconPosition === "end" && {
+ flexDirection: "row-reverse",
+ "& .MuiChip-icon": {
+ gap: 0,
+ marginRight: 10,
+ marginLeft: 0,
+ },
+ }),
+ };
+ },
+ },
+ variants: [
+ {
+ props: { color: "success" },
+ style: {
+ color: theme.palette.semantic.chip.success.text,
+ backgroundColor: theme.palette.semantic.chip.success.background,
+ },
+ },
+ {
+ props: { color: "warning" },
+ style: {
+ color: theme.palette.semantic.chip.warning.text,
+ backgroundColor: theme.palette.semantic.chip.warning.background,
+ },
+ },
+ ],
+ },
+ };
+}
+
+declare module "@mui/material/Chip" {
+ interface ChipOwnProps {
+ iconPosition?: "start" | "end";
+ }
+}
diff --git a/apps/customer-portal/microapp/src/theme/overrides/index.ts b/apps/customer-portal/microapp/src/theme/overrides/index.ts
new file mode 100644
index 000000000..f34a58fe4
--- /dev/null
+++ b/apps/customer-portal/microapp/src/theme/overrides/index.ts
@@ -0,0 +1,25 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import type { Theme } from "@mui/material";
+import Buttons from "@theme/overrides/buttons";
+import Inputs from "@theme/overrides/inputs";
+import Navigations from "@theme/overrides/navigations";
+import Chips from "./chips";
+
+export default function ComponentOverrides(theme: Theme) {
+ return Object.assign(Buttons(theme), Inputs(theme), Navigations(theme), Chips(theme));
+}
diff --git a/apps/customer-portal/microapp/src/theme/overrides/inputs.ts b/apps/customer-portal/microapp/src/theme/overrides/inputs.ts
new file mode 100644
index 000000000..caae4c8fa
--- /dev/null
+++ b/apps/customer-portal/microapp/src/theme/overrides/inputs.ts
@@ -0,0 +1,47 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import type { Theme, Components } from "@mui/material";
+
+export default function Inputs(theme: Theme): Components {
+ return {
+ MuiInputBase: {
+ styleOverrides: {
+ root: {
+ padding: 2,
+ paddingLeft: 8,
+ paddingRight: 8,
+ borderRadius: 8,
+ fontSize: theme.typography.subtitle1.fontSize,
+ outline: `1px solid ${theme.palette.semantic.border.subtle}`,
+ transition: "outline-color 0.2s ease",
+
+ "& .MuiInputBase-input": {
+ marginLeft: 2,
+ },
+
+ "&.Mui-focused": {
+ outline: `1.5px solid ${theme.palette.primary.main}`,
+ },
+
+ "& .MuiInputAdornment-root .MuiSvgIcon-root": {
+ fontSize: theme.typography.pxToRem(19),
+ },
+ },
+ },
+ },
+ };
+}
diff --git a/apps/customer-portal/microapp/src/theme/overrides/navigations.tsx b/apps/customer-portal/microapp/src/theme/overrides/navigations.tsx
new file mode 100644
index 000000000..76dac8970
--- /dev/null
+++ b/apps/customer-portal/microapp/src/theme/overrides/navigations.tsx
@@ -0,0 +1,44 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { type Theme, type Components, Typography } from "@mui/material";
+
+export default function Navigations(theme: Theme): Components {
+ return {
+ MuiPaginationItem: {
+ defaultProps: {
+ slots: {
+ previous: () => Previous,
+ next: () => Next,
+ },
+ },
+ styleOverrides: {
+ root: {
+ "&.Mui-selected": {
+ color: theme.palette.common.white,
+ },
+ },
+ },
+ },
+ MuiPagination: {
+ defaultProps: {
+ shape: "rounded",
+ color: "primary",
+ siblingCount: 0,
+ },
+ },
+ };
+}
diff --git a/apps/customer-portal/microapp/src/theme/palette.ts b/apps/customer-portal/microapp/src/theme/palette.ts
new file mode 100644
index 000000000..6bd788467
--- /dev/null
+++ b/apps/customer-portal/microapp/src/theme/palette.ts
@@ -0,0 +1,78 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+export const palette = {
+ primary: {
+ main: "#ff7300",
+ contrastText: "#ffffff",
+ },
+ semantic: {
+ border: {
+ subtle: "#e0e0e0",
+ },
+ portal: {
+ background: { main: "#f5f5f5", secondary: "#f9fafb" },
+ accent: {
+ orange: "#ff5722",
+ green: "#4caf50",
+ blue: "#5b6ef5",
+ cyan: "#00bcd4",
+ purple: "#9c27b0",
+ amber: "#ffc107",
+ },
+ },
+ chip: {
+ success: {
+ text: "#4daf50",
+ background: "#e8f5e9",
+ },
+ warning: {
+ text: "#ffc107",
+ background: "#fff8e1",
+ },
+ },
+ priority: {
+ low: {
+ background: "#f3f4f6",
+ text: "#374151",
+ },
+ normal: {
+ background: "#dbeafe",
+ text: "#1d4ed8",
+ },
+ high: {
+ background: "#fee2e2",
+ text: "#b91c1c",
+ },
+ },
+ status: {
+ wip: "#f97316",
+ waiting: "#3b82f6",
+ awaiting: "#a855f7",
+ },
+ avatar: {
+ foreground: "#ca3500",
+ background: "#ffedd4",
+ },
+ },
+ divider: "#eeeeee",
+} as const;
+
+declare module "@mui/material/styles" {
+ interface Palette {
+ semantic: typeof palette.semantic;
+ }
+}
diff --git a/apps/customer-portal/microapp/src/theme/typography.ts b/apps/customer-portal/microapp/src/theme/typography.ts
new file mode 100644
index 000000000..af64496ef
--- /dev/null
+++ b/apps/customer-portal/microapp/src/theme/typography.ts
@@ -0,0 +1,65 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+export const pxToRem = (px: number) => `${px / 16}rem`;
+
+export const typography = {
+ fontFamily: '"Plus Jakarta Sans", "Roboto", "Arial", sans-serif',
+ fontSize: 14,
+ fontWeightLight: 300,
+ fontWeightRegular: 400,
+ fontWeightMedium: 550,
+ fontWeightBold: 600,
+ h1: {
+ fontSize: pxToRem(40),
+ },
+ h2: {
+ fontSize: pxToRem(32),
+ },
+ h3: {
+ fontSize: pxToRem(28),
+ },
+ h4: {
+ fontSize: pxToRem(23),
+ },
+ h5: {
+ fontSize: pxToRem(20),
+ },
+ h6: {
+ fontSize: pxToRem(17),
+ },
+ subtitle1: {
+ fontSize: pxToRem(15),
+ },
+ subtitle2: {
+ fontSize: pxToRem(14),
+ },
+ body1: {
+ fontSize: pxToRem(16),
+ },
+ body2: {
+ fontSize: pxToRem(15.5),
+ },
+ button: {
+ fontSize: pxToRem(16),
+ },
+ caption: {
+ fontSize: pxToRem(12),
+ },
+ overline: {
+ fontSize: pxToRem(11),
+ },
+};
diff --git a/apps/customer-portal/microapp/src/utils/constants.ts b/apps/customer-portal/microapp/src/utils/constants.ts
new file mode 100644
index 000000000..90b83c9c5
--- /dev/null
+++ b/apps/customer-portal/microapp/src/utils/constants.ts
@@ -0,0 +1,24 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+export const ErrorMessages = {
+ NATIVE_BRIDGE_NOT_AVAILABLE: "Native bridge is not available",
+};
+
+export const LocalStorageKeys = {
+ accessToken: "accessToken",
+ idToken: "idToken",
+};
diff --git a/apps/customer-portal/microapp/src/utils/logger.ts b/apps/customer-portal/microapp/src/utils/logger.ts
new file mode 100644
index 000000000..693bbf6bf
--- /dev/null
+++ b/apps/customer-portal/microapp/src/utils/logger.ts
@@ -0,0 +1,37 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { sendNativeLog } from "@components/microapp-bridge";
+
+/**
+ * Logger class for logging messages to the React Native DevTools
+ */
+export class Logger {
+ // Info/Basic Logs
+ static info(message: string, data?: unknown) {
+ sendNativeLog(message, data, "info");
+ }
+
+ // Error Logs
+ static error(message: string, data?: unknown) {
+ sendNativeLog(message, data, "error");
+ }
+
+ // Warning Logs
+ static warn(message: string, data?: unknown) {
+ sendNativeLog(message, data, "warn");
+ }
+}
diff --git a/apps/customer-portal/microapp/src/utils/others.ts b/apps/customer-portal/microapp/src/utils/others.ts
new file mode 100644
index 000000000..e4accf677
--- /dev/null
+++ b/apps/customer-portal/microapp/src/utils/others.ts
@@ -0,0 +1,19 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+export const stringAvatar = (name: string) => {
+ return `${name.split(" ")[0][0]}${name.split(" ")[1][0]}`;
+};
diff --git a/apps/customer-portal/microapp/src/vite-env.d.ts b/apps/customer-portal/microapp/src/vite-env.d.ts
new file mode 100644
index 000000000..03eb28229
--- /dev/null
+++ b/apps/customer-portal/microapp/src/vite-env.d.ts
@@ -0,0 +1,28 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+///
+///
+
+type ViteTypeOptions = object;
+
+interface ImportMetaEnv {
+ readonly VITE_BACKEND_URL: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
diff --git a/apps/customer-portal/microapp/tsconfig.app.json b/apps/customer-portal/microapp/tsconfig.app.json
new file mode 100644
index 000000000..b889ccf70
--- /dev/null
+++ b/apps/customer-portal/microapp/tsconfig.app.json
@@ -0,0 +1,36 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2023",
+ "useDefineForClassFields": true,
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noImplicitAny": false
+ },
+ "include": ["src"],
+ "extends": "./tsconfig.paths.json"
+}
diff --git a/apps/customer-portal/microapp/tsconfig.json b/apps/customer-portal/microapp/tsconfig.json
new file mode 100644
index 000000000..d32ff6820
--- /dev/null
+++ b/apps/customer-portal/microapp/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "files": [],
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
+}
diff --git a/apps/customer-portal/microapp/tsconfig.node.json b/apps/customer-portal/microapp/tsconfig.node.json
new file mode 100644
index 000000000..284dce6f4
--- /dev/null
+++ b/apps/customer-portal/microapp/tsconfig.node.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": false,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noImplicitAny": false
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/apps/customer-portal/microapp/tsconfig.paths.json b/apps/customer-portal/microapp/tsconfig.paths.json
new file mode 100644
index 000000000..1e028feb7
--- /dev/null
+++ b/apps/customer-portal/microapp/tsconfig.paths.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@root/*": ["./*"],
+ "@src/*": ["./src/*"],
+ "@components/*": ["./src/components/*"],
+ "@config/*": ["./src/config/*"],
+ "@features/*": ["./src/features/*"],
+ "@icons/*": ["./src/icons/*"],
+ "@pages/*": ["./src/pages/*"],
+ "@theme/*": ["./src/theme/*"],
+ "@utils/*": ["./src/utils/*"],
+ }
+ }
+}
diff --git a/apps/customer-portal/microapp/vite.config.ts b/apps/customer-portal/microapp/vite.config.ts
new file mode 100644
index 000000000..fe33f6791
--- /dev/null
+++ b/apps/customer-portal/microapp/vite.config.ts
@@ -0,0 +1,42 @@
+// Copyright (c) 2025 WSO2 LLC. (https://www.wso2.com).
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import path from "path";
+import tsconfigPaths from "vite-tsconfig-paths";
+import svgr from "vite-plugin-svgr";
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react(), svgr(), tsconfigPaths()],
+ server: {
+ port: 3000,
+ },
+ resolve: {
+ alias: {
+ "@root": path.resolve(__dirname),
+ "@src": path.resolve(__dirname, "src"),
+ "@components": path.resolve(__dirname, "src/components"),
+ "@config": path.resolve(__dirname, "src/config"),
+ "@features": path.resolve(__dirname, "src/features"),
+ "@icons": path.resolve(__dirname, "src/icons"),
+ "@pages": path.resolve(__dirname, "src/pages"),
+ "@theme": path.resolve(__dirname, "src/theme"),
+ "@utils": path.resolve(__dirname, "src/utils"),
+ },
+ },
+});