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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ tests/__image_snapshots__/__diff_output__
!.yarn/versions
*.env.*
!.env.yarn.example
!.env.example
!.env.development
!.env.production

# --- Adversarial coding pipeline (project-local .kiro) ---
# Committed: .kiro/agents, .kiro/skills, .kiro/hooks, .kiro/scripts,
Expand Down
2 changes: 1 addition & 1 deletion packages/app-degree-pages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"postdocs": "node ../../scripts/process-readme-props.js"
},
"dependencies": {
"@asu/unity-react-core": "^1.0.0",
"@asu/unity-react-core": "^2.0.0",
"@popperjs/core": "^2.9.2",
"classnames": "^2.3.1",
"prop-types": "^15.7.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/app-rfi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
},
"devDependencies": {
"@asu/shared": "*",
"@asu/unity-bootstrap-theme": "^1.0.0",
"@asu/unity-bootstrap-theme": "*",
"@babel/core": "^7.13.14",
"@babel/eslint-parser": "^7.13.14",
"@babel/plugin-proposal-class-properties": "^7.13.0",
Expand Down Expand Up @@ -79,7 +79,7 @@
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@asu/unity-react-core": "^1.0.0",
"@asu/unity-react-core": "^2.0.0",
"formik": "^2.1.4",
"prop-types": "^15.7.2",
"react-phone-input-2": "2.15.1",
Expand Down
11 changes: 11 additions & 0 deletions packages/app-webdir-ui/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Web Directory UI - Development Environment Configuration
#
# This file is loaded by Vite when running `yarn storybook:dev`
# It enables the backend API proxy for real data testing in Storybook

# Local proxy server port used by `node server.js`
PORT=3000

# Search engine request base URL + version used by Storybook stories
VITE_API_URL=http://localhost:${PORT}/
VITE_SEARCH_API_VERSION=api/v1/
13 changes: 13 additions & 0 deletions packages/app-webdir-ui/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Web Directory UI - Environment Configuration Template
#
# Copy this file to:
# - .env.development (git-tracked, shared dev defaults)
# - .env.local (git-ignored, personal overrides)

# Local proxy server port used by `node server.js`
# PORT=3000

# API_URL + searchApiVersion used by stories/components
# Default base URL + version pair:
# VITE_API_URL=http://localhost:${PORT}/
# VITE_SEARCH_API_VERSION=/api/v1/
5 changes: 5 additions & 0 deletions packages/app-webdir-ui/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Web Directory UI - Storybook build environment
# Loaded by the build-storybook script for deterministic static build config.

VITE_API_URL=https://asuapp2dev.prod.acquia-sites.com/
VITE_SEARCH_API_VERSION=api/v1/
4 changes: 4 additions & 0 deletions packages/app-webdir-ui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ package-lock.json
yarn-lock.json
docs/**
!docs/README.props.md

# Environment overrides (personal/local config)
.env.local
.env*.local
97 changes: 95 additions & 2 deletions packages/app-webdir-ui/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,113 @@
import { dirname } from "path";
import { existsSync, readFileSync } from "fs";
import { dirname, resolve } from "path";
import { fileURLToPath } from "url";

function getAbsolutePath(value) {
return dirname(fileURLToPath(import.meta.resolve(value)));
}

const storybookDir = dirname(fileURLToPath(import.meta.url));
const packageRoot = resolve(storybookDir, "..");

function loadEnvFile(filePath) {
if (!existsSync(filePath)) {
return;
}

const fileContent = readFileSync(filePath, "utf8");
for (const rawLine of fileContent.split(/\r?\n/)) {
const line = rawLine.trim();
if (!line || line.startsWith("#")) {
continue;
}

const separatorIndex = line.indexOf("=");
if (separatorIndex < 0) {
continue;
}

const key = line.slice(0, separatorIndex).trim();
if (!key || process.env[key] !== undefined) {
continue;
}

const value = line.slice(separatorIndex + 1).trim();
process.env[key] = value.replace(/^['"]|['"]$/g, "");
}
}

function loadStorybookEnv(configType) {
const envFileName =
configType === "PRODUCTION" ? ".env.production" : ".env.development";
loadEnvFile(resolve(packageRoot, envFileName));
}

const config = {
addons: [
fileURLToPath(import.meta.resolve("../../../.storybook-config/index.js")),
fileURLToPath(import.meta.resolve("../../../.storybook-config/dataLayerListener/index.js")),
fileURLToPath(
import.meta
.resolve("../../../.storybook-config/dataLayerListener/index.js")
),
getAbsolutePath("@storybook/addon-a11y"),
],
stories: ["../src/**/*.stories.js"],
framework: {
name: getAbsolutePath("@storybook/react-vite"),
},
/**
* Configure Vite proxy for real API data in dev environment.
* Proxy target is read from env as VITE_API_URL + VITE_SEARCH_API_VERSION.
* Proxy only API routes to avoid intercepting Storybook/Vite internal assets.
*/
viteFinal: async (config, { configType }) => {
loadStorybookEnv(configType);

const apiUrl = process.env.VITE_API_URL;
const searchApiVersion = process.env.VITE_SEARCH_API_VERSION;
const target = `${apiUrl || ""}${searchApiVersion || ""}`;
const apiPath = `/${(searchApiVersion || "")
.replace(/^\/+/, "")
.replace(/\/+$/, "")}`;

if (!target) {
throw new Error(
"Missing Storybook proxy target. Set VITE_API_URL and VITE_SEARCH_API_VERSION in env files."
);
}

config.server = config.server || {};
config.server.proxy = {
[apiPath]: {
target,
changeOrigin: true,
secure: false,
logLevel: "info",
},
"/session/token": {
target,
changeOrigin: true,
secure: false,
logLevel: "info",
},
"/webdir-profiles": {
target,
changeOrigin: true,
secure: false,
logLevel: "info",
},
};

config.optimizeDeps = config.optimizeDeps || {};
config.optimizeDeps.esbuildOptions =
config.optimizeDeps.esbuildOptions || {};
config.optimizeDeps.esbuildOptions.loader = {
...(config.optimizeDeps.esbuildOptions.loader || {}),
".js": "jsx",
};

return config;
},
};

export default config;
65 changes: 41 additions & 24 deletions packages/app-webdir-ui/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect} from "react";
import React, { useEffect, useRef } from "react";
import { MemoryRouter, useLocation, useSearchParams } from "react-router-dom";
import { useArgs } from 'storybook/preview-api';
import { useArgs } from "storybook/preview-api";

import "@asu/unity-bootstrap-theme/src/scss/unity-bootstrap-theme.bundle.scss";

Expand All @@ -14,50 +14,67 @@ const argTypes = {
control: {
type: "object",
},
}
},
};

const args = {
searchParams: {}
searchParams: {},
};

const getParamObject = (paramArray = []) => {
const result = {};
for (const entry of (paramArray.entries())) {
for (const entry of paramArray.entries()) {
result[entry[0]] = entry[1];
}
return result;
}
};

const areObjectsEqual = (left = {}, right = {}) =>
JSON.stringify(left) === JSON.stringify(right);

const Wrapper = ({ args, updateArgs,...props}) => {
const Wrapper = ({ args, updateArgs, ...props }) => {
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const [, setSearchParams] = useSearchParams();
const seededFromArgsRef = useRef(false);

useEffect(()=>{
updateArgs({
...args,
searchParams: getParamObject(new URLSearchParams(location.search)),
})
},[location.search])
useEffect(() => {
const nextSearchParams = getParamObject(new URLSearchParams(location.search));
if (!areObjectsEqual(args.searchParams, nextSearchParams)) {
updateArgs({
searchParams: nextSearchParams,
});
}
}, [args.searchParams, location.search, updateArgs]);

useEffect(()=>{
setSearchParams({
...getParamObject(searchParams),
...args.searchParams,
});
},[args.searchParams])
useEffect(() => {
if (seededFromArgsRef.current) {
return;
}

const currentSearchParams = getParamObject(new URLSearchParams(location.search));
if (
Object.keys(currentSearchParams).length === 0 &&
Object.keys(args.searchParams || {}).length > 0
) {
setSearchParams(args.searchParams, { replace: true });
}

seededFromArgsRef.current = true;
}, [args.searchParams, location.search, setSearchParams]);

return props.children;
}
};

const decorators = [
(Story) => {
Story => {
const [args, updateArgs] = useArgs();
return <MemoryRouter>
return (
<MemoryRouter>
<Wrapper args={args} updateArgs={updateArgs}>
<Story />
</Wrapper>
</MemoryRouter>
</MemoryRouter>
);
},
];

Expand Down
Loading