configuration-management 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -0
- package/actions/configurations/commerce/index.js +55 -0
- package/actions/configurations/export-config/index.js +259 -0
- package/actions/configurations/ext.config.yaml +151 -0
- package/actions/configurations/import-config/index.js +544 -0
- package/actions/configurations/registration/index.js +37 -0
- package/actions/configurations/sync-store-mappings-from-commerce/index.js +199 -0
- package/actions/configurations/system-config-list/index.js +127 -0
- package/actions/configurations/system-config-save/index.js +160 -0
- package/actions/configurations/system-config-schema/index.js +327 -0
- package/actions/utils.js +73 -0
- package/package.json +74 -0
- package/scripts/setup-app-config.js +114 -0
- package/src/abdb-config.js +241 -0
- package/src/abdb-helper.js +476 -0
- package/src/index.js +20 -0
- package/src/oauth1a.js +135 -0
- package/src/system-config-crypto.js +113 -0
- package/src/system-config-shared.js +89 -0
- package/web/src/components/App.js +47 -0
- package/web/src/components/AppSectionNav.js +49 -0
- package/web/src/components/ExtensionRegistration.js +33 -0
- package/web/src/components/MainPage.js +46 -0
- package/web/src/components/SystemConfig.js +1464 -0
- package/web/src/components/SystemConfigSchemaEditor.js +459 -0
- package/web/src/hooks/useConfirm.js +355 -0
- package/web/src/hooks/useSystemConfig.js +238 -0
- package/web/src/hooks/useSystemConfigSchema.js +102 -0
- package/web/src/index.js +41 -0
- package/web/src/schema/systemConfigSchema.js +82 -0
- package/web/src/settings.js +57 -0
- package/web/src/styles/index.css +326 -0
- package/web/src/theme.js +104 -0
- package/web/src/utils/storeMappingsFromCommerceRest.js +73 -0
- package/web/src/utils.js +52 -0
package/web/src/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import App from './components/App'
|
|
9
|
+
import { MainPage } from './components/MainPage'
|
|
10
|
+
import ExtensionRegistration from './components/ExtensionRegistration'
|
|
11
|
+
import SystemConfig from './components/SystemConfig'
|
|
12
|
+
import SystemConfigSchemaEditor from './components/SystemConfigSchemaEditor'
|
|
13
|
+
import AppSectionNav, { NAV_ITEMS } from './components/AppSectionNav'
|
|
14
|
+
|
|
15
|
+
export { useSystemConfig } from './hooks/useSystemConfig'
|
|
16
|
+
export { useSystemConfigSchema } from './hooks/useSystemConfigSchema'
|
|
17
|
+
export { useConfirm } from './hooks/useConfirm'
|
|
18
|
+
|
|
19
|
+
export * from './schema/systemConfigSchema'
|
|
20
|
+
export { buildStoreMappingsFromCommercePayload } from './utils/storeMappingsFromCommerceRest'
|
|
21
|
+
export { callAction } from './utils'
|
|
22
|
+
export {
|
|
23
|
+
configureWeb,
|
|
24
|
+
getExtensionId,
|
|
25
|
+
getActionKey,
|
|
26
|
+
DEFAULT_ACTION_KEYS
|
|
27
|
+
} from './settings'
|
|
28
|
+
export { THEME, PALETTE, RADIUS, SHADOW, SPACE, FONT } from './theme'
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
App,
|
|
32
|
+
MainPage,
|
|
33
|
+
ExtensionRegistration,
|
|
34
|
+
SystemConfig,
|
|
35
|
+
SystemConfigSchemaEditor,
|
|
36
|
+
AppSectionNav,
|
|
37
|
+
NAV_ITEMS
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Full Commerce Admin extension shell (router + Spectrum provider). */
|
|
41
|
+
export { default as ConfigurationManagementApp } from './components/App'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema is no longer defined statically — it is stored in App Builder DB
|
|
10
|
+
* and managed entirely through the Schema Designer UI. This module only
|
|
11
|
+
* exposes helpers for working with whatever schema shape is loaded at runtime.
|
|
12
|
+
*
|
|
13
|
+
* Schema shape (returned from the system-config-schema action):
|
|
14
|
+
* {
|
|
15
|
+
* sections: [{
|
|
16
|
+
* id, label,
|
|
17
|
+
* groups: [{
|
|
18
|
+
* id, label,
|
|
19
|
+
* fields: [{
|
|
20
|
+
* id, label, type, default,
|
|
21
|
+
* showIn: ['default' | 'websites' | 'stores'],
|
|
22
|
+
* sensitive?: boolean,
|
|
23
|
+
* options?: [{ value, label }] // type === 'select'
|
|
24
|
+
* }]
|
|
25
|
+
* }]
|
|
26
|
+
* }]
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export const FIELD_TYPES = ['text', 'textarea', 'password', 'number', 'select', 'boolean']
|
|
31
|
+
export const SCOPES = ['default', 'websites', 'stores']
|
|
32
|
+
|
|
33
|
+
const SENSITIVE_FIELD_TYPES = new Set(['password'])
|
|
34
|
+
|
|
35
|
+
export function emptySchema () {
|
|
36
|
+
return { sections: [] }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getFieldPath (sectionId, groupId, fieldId) {
|
|
40
|
+
return `${sectionId}/${groupId}/${fieldId}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isFieldSensitive (field) {
|
|
44
|
+
return !!field?.sensitive || SENSITIVE_FIELD_TYPES.has(field?.type)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isFieldVisibleAtScope (field, scope) {
|
|
48
|
+
const allowed = field?.showIn || ['default']
|
|
49
|
+
return allowed.includes(scope)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function flattenFields (schema) {
|
|
53
|
+
const out = []
|
|
54
|
+
if (!schema || !Array.isArray(schema.sections)) return out
|
|
55
|
+
for (const section of schema.sections) {
|
|
56
|
+
if (!Array.isArray(section.groups)) continue
|
|
57
|
+
for (const group of section.groups) {
|
|
58
|
+
if (!Array.isArray(group.fields)) continue
|
|
59
|
+
for (const field of group.fields) {
|
|
60
|
+
out.push({
|
|
61
|
+
section,
|
|
62
|
+
group,
|
|
63
|
+
field,
|
|
64
|
+
path: getFieldPath(section.id, group.id, field.id),
|
|
65
|
+
sensitive: isFieldSensitive(field)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function coerceDefault (field) {
|
|
74
|
+
switch (field?.type) {
|
|
75
|
+
case 'boolean':
|
|
76
|
+
return !!field.default
|
|
77
|
+
case 'number':
|
|
78
|
+
return typeof field.default === 'number' ? field.default : Number(field.default) || 0
|
|
79
|
+
default:
|
|
80
|
+
return field?.default ?? ''
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Default OpenWhisk action keys (must match keys in deploy-time config.json). */
|
|
9
|
+
export const DEFAULT_ACTION_KEYS = {
|
|
10
|
+
commerceRestGet: 'ConfigurationManagement/commerce-rest-get',
|
|
11
|
+
systemConfigList: 'ConfigurationManagement/system-config-list',
|
|
12
|
+
systemConfigSave: 'ConfigurationManagement/system-config-save',
|
|
13
|
+
systemConfigSchema: 'ConfigurationManagement/system-config-schema',
|
|
14
|
+
exportConfig: 'ConfigurationManagement/export-config',
|
|
15
|
+
importConfig: 'ConfigurationManagement/import-config',
|
|
16
|
+
syncStoreMappings: 'ConfigurationManagement/sync-store-mappings-from-commerce'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let extensionId = 'ConfigurationManagement'
|
|
20
|
+
let actionUrls = {}
|
|
21
|
+
let actionKeys = { ...DEFAULT_ACTION_KEYS }
|
|
22
|
+
|
|
23
|
+
export function getExtensionId () {
|
|
24
|
+
return extensionId
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getActionKey (name) {
|
|
28
|
+
return actionKeys[name] || name
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getActionUrl (actionKey) {
|
|
32
|
+
return actionUrls[actionKey]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configure the web UI before rendering.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} [options]
|
|
39
|
+
* @param {string} [options.extensionId] - UIX guest extension id
|
|
40
|
+
* @param {Record<string, string>} [options.actionUrls] - map of action key → deployed URL (from config.json)
|
|
41
|
+
* @param {Partial<typeof DEFAULT_ACTION_KEYS>} [options.actionKeys] - override default action key names
|
|
42
|
+
*/
|
|
43
|
+
export function configureWeb ({
|
|
44
|
+
extensionId: nextExtensionId,
|
|
45
|
+
actionUrls: nextActionUrls,
|
|
46
|
+
actionKeys: nextActionKeys
|
|
47
|
+
} = {}) {
|
|
48
|
+
if (nextExtensionId != null) {
|
|
49
|
+
extensionId = String(nextExtensionId)
|
|
50
|
+
}
|
|
51
|
+
if (nextActionUrls) {
|
|
52
|
+
actionUrls = { ...nextActionUrls }
|
|
53
|
+
}
|
|
54
|
+
if (nextActionKeys) {
|
|
55
|
+
actionKeys = { ...actionKeys, ...nextActionKeys }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* =========================================================================
|
|
9
|
+
sync-management — single source of truth for the UI theme.
|
|
10
|
+
|
|
11
|
+
To re-skin the app, change values under :root. Components consume these
|
|
12
|
+
tokens either directly via CSS classes (.sm-*) or via the JS facade in
|
|
13
|
+
`web-src/src/theme.js` which re-exports them as `var(--sm-*)` strings.
|
|
14
|
+
|
|
15
|
+
Token naming:
|
|
16
|
+
--sm-color-* colors
|
|
17
|
+
--sm-radius-* border radii
|
|
18
|
+
--sm-space-* spacing scale
|
|
19
|
+
--sm-shadow-* elevation
|
|
20
|
+
--sm-font-* typography
|
|
21
|
+
--sm-control-* interactive control sizing
|
|
22
|
+
========================================================================= */
|
|
23
|
+
|
|
24
|
+
:root {
|
|
25
|
+
/* ---- Colors --------------------------------------------------------- */
|
|
26
|
+
--sm-color-bg: #f7f8fa;
|
|
27
|
+
--sm-color-surface: #ffffff;
|
|
28
|
+
--sm-color-surface-muted: #f3f4f6;
|
|
29
|
+
--sm-color-surface-subtle: #fafbfc;
|
|
30
|
+
--sm-color-border: #e5e7eb;
|
|
31
|
+
--sm-color-border-strong: #d1d5db;
|
|
32
|
+
--sm-color-text: #111827;
|
|
33
|
+
--sm-color-text-muted: #6b7280;
|
|
34
|
+
--sm-color-text-inverse: #ffffff;
|
|
35
|
+
|
|
36
|
+
--sm-color-accent: #1473e6;
|
|
37
|
+
--sm-color-accent-hover: #0f5fc4;
|
|
38
|
+
--sm-color-accent-soft: #e8f1fc;
|
|
39
|
+
|
|
40
|
+
--sm-color-success: #22863a;
|
|
41
|
+
--sm-color-success-hover: #1a6e2f;
|
|
42
|
+
--sm-color-success-soft: #ecfdf5;
|
|
43
|
+
--sm-color-warning: #b58105;
|
|
44
|
+
--sm-color-warning-hover: #946c04;
|
|
45
|
+
--sm-color-warning-soft: #fff7ed;
|
|
46
|
+
--sm-color-warning-border: #fde68a;
|
|
47
|
+
--sm-color-warning-text: #92400e;
|
|
48
|
+
--sm-color-warning-tint: #fef3c7;
|
|
49
|
+
--sm-color-danger: #c0392b;
|
|
50
|
+
--sm-color-danger-hover: #a32d20;
|
|
51
|
+
--sm-color-danger-soft: #fef2f2;
|
|
52
|
+
--sm-color-danger-tint: #fee2e2;
|
|
53
|
+
--sm-color-accent-tint: #dbeafe;
|
|
54
|
+
|
|
55
|
+
--sm-color-neutral-soft: #eef2f7;
|
|
56
|
+
--sm-color-neutral-text: #374151;
|
|
57
|
+
|
|
58
|
+
/* Surfaces used by the modal scaffolding (header text, body text, panel). */
|
|
59
|
+
--sm-color-text-strong: #1f2937;
|
|
60
|
+
--sm-color-text-soft: #475569;
|
|
61
|
+
--sm-color-surface-panel: #f9fafb;
|
|
62
|
+
|
|
63
|
+
/* Modal / overlay scrim */
|
|
64
|
+
--sm-color-overlay: rgba(15, 23, 42, 0.45);
|
|
65
|
+
|
|
66
|
+
/* ---- Radii ---------------------------------------------------------- */
|
|
67
|
+
--sm-radius-sm: 4px;
|
|
68
|
+
--sm-radius-md: 8px;
|
|
69
|
+
--sm-radius-lg: 10px;
|
|
70
|
+
--sm-radius-xl: 12px;
|
|
71
|
+
--sm-radius-2xl: 14px;
|
|
72
|
+
--sm-radius-pill: 999px;
|
|
73
|
+
|
|
74
|
+
/* ---- Spacing -------------------------------------------------------- */
|
|
75
|
+
--sm-space-1: 4px;
|
|
76
|
+
--sm-space-2: 8px;
|
|
77
|
+
--sm-space-3: 12px;
|
|
78
|
+
--sm-space-4: 16px;
|
|
79
|
+
--sm-space-5: 20px;
|
|
80
|
+
--sm-space-6: 24px;
|
|
81
|
+
|
|
82
|
+
/* ---- Shadows -------------------------------------------------------- */
|
|
83
|
+
--sm-shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.03);
|
|
84
|
+
--sm-shadow-sm: 0 1px 4px rgba(15, 23, 42, 0.04);
|
|
85
|
+
--sm-shadow-md: 0 1px 3px rgba(15, 23, 42, 0.12), 0 1px 1px rgba(15, 23, 42, 0.06);
|
|
86
|
+
--sm-shadow-pill: 0 1px 3px rgba(15, 23, 42, 0.10), 0 1px 1px rgba(15, 23, 42, 0.06);
|
|
87
|
+
--sm-shadow-floating: 0 4px 14px rgba(15, 23, 42, 0.08);
|
|
88
|
+
--sm-shadow-dropdown: 0 12px 28px rgba(15, 23, 42, 0.16), 0 2px 4px rgba(15, 23, 42, 0.06);
|
|
89
|
+
--sm-shadow-modal: 0 24px 60px rgba(15, 23, 42, 0.25), 0 2px 8px rgba(15, 23, 42, 0.08);
|
|
90
|
+
--sm-shadow-inset: inset 0 1px 2px rgba(15, 23, 42, 0.04);
|
|
91
|
+
|
|
92
|
+
/* ---- Typography ----------------------------------------------------- */
|
|
93
|
+
--sm-font-family: adobe-clean, 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
94
|
+
--sm-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
95
|
+
--sm-font-size-xs: 11px;
|
|
96
|
+
--sm-font-size-sm: 12px;
|
|
97
|
+
--sm-font-size-md: 13px;
|
|
98
|
+
--sm-font-size-lg: 14px;
|
|
99
|
+
--sm-font-weight-regular: 400;
|
|
100
|
+
--sm-font-weight-medium: 500;
|
|
101
|
+
--sm-font-weight-semibold:600;
|
|
102
|
+
--sm-font-weight-bold: 700;
|
|
103
|
+
|
|
104
|
+
/* ---- Control sizing ------------------------------------------------- */
|
|
105
|
+
--sm-control-height-sm: 28px;
|
|
106
|
+
--sm-control-height-md: 32px;
|
|
107
|
+
--sm-control-height-lg: 40px;
|
|
108
|
+
--sm-control-padding-x: 16px;
|
|
109
|
+
|
|
110
|
+
/* ---- Z-index -------------------------------------------------------- */
|
|
111
|
+
--sm-z-nav: 30;
|
|
112
|
+
--sm-z-sticky: 20;
|
|
113
|
+
--sm-z-modal: 100;
|
|
114
|
+
|
|
115
|
+
/* =====================================================================
|
|
116
|
+
Spectrum token overrides — re-skin React-Spectrum widgets without
|
|
117
|
+
forking the theme. Keep these in sync with the --sm-* tokens above
|
|
118
|
+
so default Buttons / TextField / Picker pick up our accent automatically.
|
|
119
|
+
===================================================================== */
|
|
120
|
+
--spectrum-accent-color-900: var(--sm-color-accent);
|
|
121
|
+
--spectrum-accent-color-1000: var(--sm-color-accent-hover);
|
|
122
|
+
/* Text colours are left to Spectrum so secondary/quiet buttons keep
|
|
123
|
+
their proper contrast on both light and dark surfaces. */
|
|
124
|
+
|
|
125
|
+
color-scheme: light;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* =========================================================================
|
|
129
|
+
Base
|
|
130
|
+
========================================================================= */
|
|
131
|
+
html,
|
|
132
|
+
body,
|
|
133
|
+
#root {
|
|
134
|
+
margin: 0;
|
|
135
|
+
min-height: 100%;
|
|
136
|
+
background: var(--sm-color-bg);
|
|
137
|
+
color: var(--sm-color-text);
|
|
138
|
+
font-family: var(--sm-font-family);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* React-Spectrum's <Provider> wraps its tree in a div. We mark it with
|
|
142
|
+
`UNSAFE_className="sm-provider"` and set its background here so the page
|
|
143
|
+
bg fills the whole iframe — but we only paint the wrapper, NOT any
|
|
144
|
+
descendants. Painting descendants stomps on inputs / buttons / pickers. */
|
|
145
|
+
.sm-provider {
|
|
146
|
+
background: var(--sm-color-bg);
|
|
147
|
+
min-height: 100vh;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* =========================================================================
|
|
151
|
+
Cards
|
|
152
|
+
========================================================================= */
|
|
153
|
+
.sm-card {
|
|
154
|
+
background: var(--sm-color-surface);
|
|
155
|
+
border: 1px solid var(--sm-color-border);
|
|
156
|
+
border-radius: var(--sm-radius-lg);
|
|
157
|
+
box-shadow: var(--sm-shadow-xs);
|
|
158
|
+
padding: var(--sm-space-5);
|
|
159
|
+
}
|
|
160
|
+
.sm-card--flush { padding: 0; }
|
|
161
|
+
|
|
162
|
+
/* =========================================================================
|
|
163
|
+
Pills
|
|
164
|
+
========================================================================= */
|
|
165
|
+
.sm-pill {
|
|
166
|
+
display: inline-flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: var(--sm-space-1);
|
|
169
|
+
padding: 2px var(--sm-space-2);
|
|
170
|
+
border-radius: var(--sm-radius-pill);
|
|
171
|
+
background: var(--sm-color-neutral-soft);
|
|
172
|
+
color: var(--sm-color-neutral-text);
|
|
173
|
+
font-size: var(--sm-font-size-xs);
|
|
174
|
+
font-weight: var(--sm-font-weight-semibold);
|
|
175
|
+
line-height: 16px;
|
|
176
|
+
letter-spacing: 0.2px;
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
}
|
|
179
|
+
.sm-pill--accent { background: var(--sm-color-accent-soft); color: var(--sm-color-accent); }
|
|
180
|
+
.sm-pill--success { background: var(--sm-color-success-soft); color: var(--sm-color-success); }
|
|
181
|
+
.sm-pill--warning { background: var(--sm-color-warning-soft); color: var(--sm-color-warning); }
|
|
182
|
+
.sm-pill--danger { background: var(--sm-color-danger-soft); color: var(--sm-color-danger); }
|
|
183
|
+
|
|
184
|
+
/* =========================================================================
|
|
185
|
+
Tab bar (used by AppSectionNav)
|
|
186
|
+
========================================================================= */
|
|
187
|
+
.sm-tab-bar {
|
|
188
|
+
position: sticky;
|
|
189
|
+
top: 0;
|
|
190
|
+
z-index: var(--sm-z-nav);
|
|
191
|
+
background: var(--sm-color-surface);
|
|
192
|
+
border-bottom: 1px solid var(--sm-color-border);
|
|
193
|
+
padding: 10px var(--sm-space-4);
|
|
194
|
+
box-shadow: var(--sm-shadow-sm);
|
|
195
|
+
box-sizing: border-box;
|
|
196
|
+
max-width: 100%;
|
|
197
|
+
}
|
|
198
|
+
.sm-tab-bar__track {
|
|
199
|
+
display: inline-flex;
|
|
200
|
+
padding: var(--sm-space-1);
|
|
201
|
+
background: var(--sm-color-surface-muted);
|
|
202
|
+
border: 1px solid var(--sm-color-border);
|
|
203
|
+
border-radius: var(--sm-radius-pill);
|
|
204
|
+
box-shadow: var(--sm-shadow-inset);
|
|
205
|
+
gap: 2px;
|
|
206
|
+
font-family: var(--sm-font-family);
|
|
207
|
+
}
|
|
208
|
+
.sm-tab {
|
|
209
|
+
display: inline-flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
gap: var(--sm-space-2);
|
|
212
|
+
padding: var(--sm-space-2) var(--sm-control-padding-x);
|
|
213
|
+
border: 0;
|
|
214
|
+
border-radius: var(--sm-radius-pill);
|
|
215
|
+
background: transparent;
|
|
216
|
+
color: var(--sm-color-neutral-text);
|
|
217
|
+
font-size: var(--sm-font-size-md);
|
|
218
|
+
font-weight: var(--sm-font-weight-semibold);
|
|
219
|
+
letter-spacing: 0.1px;
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
|
|
222
|
+
}
|
|
223
|
+
.sm-tab:hover {
|
|
224
|
+
background: var(--sm-color-surface);
|
|
225
|
+
color: var(--sm-color-text);
|
|
226
|
+
}
|
|
227
|
+
.sm-tab.is-active {
|
|
228
|
+
background: var(--sm-color-surface);
|
|
229
|
+
color: var(--sm-color-accent);
|
|
230
|
+
font-weight: var(--sm-font-weight-bold);
|
|
231
|
+
box-shadow: var(--sm-shadow-pill);
|
|
232
|
+
cursor: default;
|
|
233
|
+
}
|
|
234
|
+
.sm-tab__icon { display: inline-flex; opacity: 0.75; }
|
|
235
|
+
.sm-tab.is-active .sm-tab__icon { opacity: 1; }
|
|
236
|
+
|
|
237
|
+
/* =========================================================================
|
|
238
|
+
Spectrum textareas inside the system-config field renderer
|
|
239
|
+
========================================================================= */
|
|
240
|
+
.sm-textarea textarea {
|
|
241
|
+
height: 160px !important;
|
|
242
|
+
max-height: 160px !important;
|
|
243
|
+
min-height: 160px !important;
|
|
244
|
+
overflow-y: auto !important;
|
|
245
|
+
resize: none !important;
|
|
246
|
+
font-family: var(--sm-font-mono);
|
|
247
|
+
font-size: var(--sm-font-size-sm);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* =========================================================================
|
|
251
|
+
Note banner (used by ComingSoon, info callouts, etc.)
|
|
252
|
+
========================================================================= */
|
|
253
|
+
.sm-note {
|
|
254
|
+
margin-top: var(--sm-space-3);
|
|
255
|
+
padding: var(--sm-space-3);
|
|
256
|
+
border-radius: var(--sm-radius-md);
|
|
257
|
+
background: var(--sm-color-surface-muted);
|
|
258
|
+
border: 1px solid var(--sm-color-border);
|
|
259
|
+
color: var(--sm-color-text);
|
|
260
|
+
font-size: var(--sm-font-size-md);
|
|
261
|
+
white-space: pre-line;
|
|
262
|
+
font-family: var(--sm-font-mono);
|
|
263
|
+
}
|
|
264
|
+
.sm-note--warning {
|
|
265
|
+
background: var(--sm-color-warning-soft);
|
|
266
|
+
border-color: var(--sm-color-warning-border);
|
|
267
|
+
color: var(--sm-color-warning-text);
|
|
268
|
+
font-family: var(--sm-font-family);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* =========================================================================
|
|
272
|
+
Animations (used by progress / status indicators)
|
|
273
|
+
========================================================================= */
|
|
274
|
+
@keyframes sm-pulse {
|
|
275
|
+
0%, 100% { opacity: 1; }
|
|
276
|
+
50% { opacity: 0.3; }
|
|
277
|
+
}
|
|
278
|
+
@keyframes sm-indeterminate {
|
|
279
|
+
0% { left: -40%; width: 40%; }
|
|
280
|
+
50% { left: 30%; width: 40%; }
|
|
281
|
+
100% { left: 100%; width: 40%; }
|
|
282
|
+
}
|
|
283
|
+
@keyframes sm-fade-in {
|
|
284
|
+
from { opacity: 0; }
|
|
285
|
+
to { opacity: 1; }
|
|
286
|
+
}
|
|
287
|
+
@keyframes sm-pop-in {
|
|
288
|
+
from { opacity: 0; transform: translateY(8px) scale(0.98); }
|
|
289
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* =========================================================================
|
|
293
|
+
Legacy SideNav classes (kept for backwards compat with any remaining
|
|
294
|
+
menu markup; safe to delete once nothing references them).
|
|
295
|
+
========================================================================= */
|
|
296
|
+
.SideNav {
|
|
297
|
+
list-style-type: none;
|
|
298
|
+
margin: 0;
|
|
299
|
+
padding: 0;
|
|
300
|
+
outline: none;
|
|
301
|
+
height: 100%;
|
|
302
|
+
}
|
|
303
|
+
.SideNav-item {
|
|
304
|
+
list-style-type: none;
|
|
305
|
+
margin: var(--spectrum-global-dimension-size-50) 0;
|
|
306
|
+
}
|
|
307
|
+
.SideNav-itemLink {
|
|
308
|
+
position: relative;
|
|
309
|
+
display: inline-flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
box-sizing: border-box;
|
|
312
|
+
width: 100%;
|
|
313
|
+
padding: var(--sm-space-2) var(--sm-space-3);
|
|
314
|
+
border-radius: var(--sm-radius-sm);
|
|
315
|
+
font-size: var(--sm-font-size-lg);
|
|
316
|
+
font-weight: var(--sm-font-weight-regular);
|
|
317
|
+
text-decoration: none;
|
|
318
|
+
word-break: break-word;
|
|
319
|
+
cursor: pointer;
|
|
320
|
+
background: transparent;
|
|
321
|
+
color: var(--sm-color-text);
|
|
322
|
+
}
|
|
323
|
+
.SideNav-itemLink.is-selected {
|
|
324
|
+
color: var(--sm-color-accent);
|
|
325
|
+
background: var(--sm-color-accent-soft);
|
|
326
|
+
}
|
package/web/src/theme.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* JS facade for the design tokens defined in `index.css`.
|
|
10
|
+
*
|
|
11
|
+
* Every value here is a CSS `var()` reference. Use these in inline styles
|
|
12
|
+
* (`style={{ color: THEME.color.accent }}`) so the rendered value resolves
|
|
13
|
+
* through CSS at runtime — changing the variable in `web/src/styles/index.css` re-skins
|
|
14
|
+
* everything that uses this object without touching any JS.
|
|
15
|
+
*
|
|
16
|
+
* Naming mirrors the CSS variable structure: --sm-color-accent → color.accent.
|
|
17
|
+
*/
|
|
18
|
+
export const THEME = {
|
|
19
|
+
color: {
|
|
20
|
+
bg: 'var(--sm-color-bg)',
|
|
21
|
+
surface: 'var(--sm-color-surface)',
|
|
22
|
+
surfaceMuted: 'var(--sm-color-surface-muted)',
|
|
23
|
+
surfaceSubtle: 'var(--sm-color-surface-subtle)',
|
|
24
|
+
border: 'var(--sm-color-border)',
|
|
25
|
+
borderStrong: 'var(--sm-color-border-strong)',
|
|
26
|
+
text: 'var(--sm-color-text)',
|
|
27
|
+
textMuted: 'var(--sm-color-text-muted)',
|
|
28
|
+
textStrong: 'var(--sm-color-text-strong)',
|
|
29
|
+
textSoft: 'var(--sm-color-text-soft)',
|
|
30
|
+
textInverse: 'var(--sm-color-text-inverse)',
|
|
31
|
+
surfacePanel: 'var(--sm-color-surface-panel)',
|
|
32
|
+
accent: 'var(--sm-color-accent)',
|
|
33
|
+
accentHover: 'var(--sm-color-accent-hover)',
|
|
34
|
+
accentSoft: 'var(--sm-color-accent-soft)',
|
|
35
|
+
accentTint: 'var(--sm-color-accent-tint)',
|
|
36
|
+
success: 'var(--sm-color-success)',
|
|
37
|
+
successHover: 'var(--sm-color-success-hover)',
|
|
38
|
+
successSoft: 'var(--sm-color-success-soft)',
|
|
39
|
+
warning: 'var(--sm-color-warning)',
|
|
40
|
+
warningHover: 'var(--sm-color-warning-hover)',
|
|
41
|
+
warningSoft: 'var(--sm-color-warning-soft)',
|
|
42
|
+
warningBorder: 'var(--sm-color-warning-border)',
|
|
43
|
+
warningText: 'var(--sm-color-warning-text)',
|
|
44
|
+
warningTint: 'var(--sm-color-warning-tint)',
|
|
45
|
+
danger: 'var(--sm-color-danger)',
|
|
46
|
+
dangerHover: 'var(--sm-color-danger-hover)',
|
|
47
|
+
dangerSoft: 'var(--sm-color-danger-soft)',
|
|
48
|
+
dangerTint: 'var(--sm-color-danger-tint)',
|
|
49
|
+
neutralSoft: 'var(--sm-color-neutral-soft)',
|
|
50
|
+
neutralText: 'var(--sm-color-neutral-text)',
|
|
51
|
+
overlay: 'var(--sm-color-overlay)'
|
|
52
|
+
},
|
|
53
|
+
radius: {
|
|
54
|
+
sm: 'var(--sm-radius-sm)',
|
|
55
|
+
md: 'var(--sm-radius-md)',
|
|
56
|
+
lg: 'var(--sm-radius-lg)',
|
|
57
|
+
xl: 'var(--sm-radius-xl)',
|
|
58
|
+
xxl: 'var(--sm-radius-2xl)',
|
|
59
|
+
pill: 'var(--sm-radius-pill)'
|
|
60
|
+
},
|
|
61
|
+
space: {
|
|
62
|
+
1: 'var(--sm-space-1)',
|
|
63
|
+
2: 'var(--sm-space-2)',
|
|
64
|
+
3: 'var(--sm-space-3)',
|
|
65
|
+
4: 'var(--sm-space-4)',
|
|
66
|
+
5: 'var(--sm-space-5)',
|
|
67
|
+
6: 'var(--sm-space-6)'
|
|
68
|
+
},
|
|
69
|
+
shadow: {
|
|
70
|
+
xs: 'var(--sm-shadow-xs)',
|
|
71
|
+
sm: 'var(--sm-shadow-sm)',
|
|
72
|
+
md: 'var(--sm-shadow-md)',
|
|
73
|
+
pill: 'var(--sm-shadow-pill)',
|
|
74
|
+
floating: 'var(--sm-shadow-floating)',
|
|
75
|
+
dropdown: 'var(--sm-shadow-dropdown)',
|
|
76
|
+
modal: 'var(--sm-shadow-modal)',
|
|
77
|
+
inset: 'var(--sm-shadow-inset)'
|
|
78
|
+
},
|
|
79
|
+
font: {
|
|
80
|
+
family: 'var(--sm-font-family)',
|
|
81
|
+
mono: 'var(--sm-font-mono)',
|
|
82
|
+
sizeXs: 'var(--sm-font-size-xs)',
|
|
83
|
+
sizeSm: 'var(--sm-font-size-sm)',
|
|
84
|
+
sizeMd: 'var(--sm-font-size-md)',
|
|
85
|
+
sizeLg: 'var(--sm-font-size-lg)',
|
|
86
|
+
weightRegular: 'var(--sm-font-weight-regular)',
|
|
87
|
+
weightMedium: 'var(--sm-font-weight-medium)',
|
|
88
|
+
weightSemi: 'var(--sm-font-weight-semibold)',
|
|
89
|
+
weightBold: 'var(--sm-font-weight-bold)'
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Flat alias of `THEME.color` — every colour token reachable as `PALETTE.x`.
|
|
95
|
+
*
|
|
96
|
+
* Components should use this for inline styles so there is exactly one
|
|
97
|
+
* facade for the design system: change a CSS variable in `web/src/styles/index.css` →
|
|
98
|
+
* every reference here resolves to the new value at runtime.
|
|
99
|
+
*/
|
|
100
|
+
export const PALETTE = { ...THEME.color }
|
|
101
|
+
export const RADIUS = { ...THEME.radius }
|
|
102
|
+
export const SHADOW = { ...THEME.shadow }
|
|
103
|
+
export const SPACE = { ...THEME.space }
|
|
104
|
+
export const FONT = { ...THEME.font }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** @param {string} locale e.g. en_US → en */
|
|
9
|
+
function localeToLanguageCode (locale) {
|
|
10
|
+
if (locale == null || locale === '') return null
|
|
11
|
+
const s = String(locale).trim()
|
|
12
|
+
const head = s.split(/[-_]/u)[0]
|
|
13
|
+
if (head && /^[a-zA-Z]{2,8}$/.test(head)) return head.toLowerCase()
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** e.g. en_ch → en, de_ch → de */
|
|
18
|
+
function inferLanguageFromStoreCode (code) {
|
|
19
|
+
if (typeof code !== 'string') return null
|
|
20
|
+
const m = /^([a-z]{2})[-_]/i.exec(code)
|
|
21
|
+
return m ? m[1].toLowerCase() : null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build `general/settings/store_mappings` shape from Commerce REST payloads
|
|
26
|
+
* (same shape as server middleware expects: keyed by store view id string).
|
|
27
|
+
*
|
|
28
|
+
* @param {object[]|null|undefined} websitesRaw from `store/websites`
|
|
29
|
+
* @param {object[]|null|undefined} storeViewsRaw from `store/storeViews`
|
|
30
|
+
* @param {object[]|null|undefined} storeConfigsRaw from `store/storeConfigs` (optional)
|
|
31
|
+
* @returns {Record<string, { code: string, language_code: string, website_code: string, website_id: string }>}
|
|
32
|
+
*/
|
|
33
|
+
export function buildStoreMappingsFromCommercePayload (websitesRaw, storeViewsRaw, storeConfigsRaw) {
|
|
34
|
+
const websiteIdToCode = new Map()
|
|
35
|
+
if (Array.isArray(websitesRaw)) {
|
|
36
|
+
for (const w of websitesRaw) {
|
|
37
|
+
if (w && w.id != null && w.code != null) {
|
|
38
|
+
websiteIdToCode.set(String(w.id), String(w.code))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const storeCodeToLocale = new Map()
|
|
44
|
+
if (Array.isArray(storeConfigsRaw)) {
|
|
45
|
+
for (const cfg of storeConfigsRaw) {
|
|
46
|
+
if (cfg && cfg.code != null && cfg.locale != null) {
|
|
47
|
+
storeCodeToLocale.set(String(cfg.code), String(cfg.locale))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const mappings = {}
|
|
53
|
+
if (!Array.isArray(storeViewsRaw)) return mappings
|
|
54
|
+
|
|
55
|
+
for (const s of storeViewsRaw) {
|
|
56
|
+
if (!s || s.id == null || s.code == null) continue
|
|
57
|
+
const id = String(s.id)
|
|
58
|
+
const code = String(s.code)
|
|
59
|
+
const websiteId = s.website_id != null ? String(s.website_id) : ''
|
|
60
|
+
const websiteCode = websiteIdToCode.get(websiteId) || ''
|
|
61
|
+
const languageCode =
|
|
62
|
+
localeToLanguageCode(storeCodeToLocale.get(code)) ||
|
|
63
|
+
inferLanguageFromStoreCode(code) ||
|
|
64
|
+
'en'
|
|
65
|
+
mappings[id] = {
|
|
66
|
+
code,
|
|
67
|
+
language_code: languageCode,
|
|
68
|
+
website_code: websiteCode,
|
|
69
|
+
website_id: websiteId
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return mappings
|
|
73
|
+
}
|