Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions adyen-platform-experience-web-vue/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

node_modules
.DS_Store
dist
coverage
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

*.tsbuildinfo

.eslintcache

# Vitest
__screenshots__/
18 changes: 18 additions & 0 deletions adyen-platform-experience-web-vue/.i18nrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"translationSourcePaths": [
"src/assets/translations/en-US.json"
],
"locales": [
"da-DK",
"de-DE",
"es-ES",
"fi-FI",
"fr-FR",
"it-IT",
"nl-NL",
"no-NO",
"pt-BR",
"sv-SE"
],
"placeholderFormat": "YAML"
}
33 changes: 33 additions & 0 deletions adyen-platform-experience-web-vue/.storybook/dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { spawnSync } from 'child_process';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync, readFileSync } from 'fs';
import dotenv from 'dotenv';

const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = resolve(__dirname, '..');
const monorepoRoot = resolve(rootDir, '..');

// Load .env from this project's envs/ or fall back to monorepo envs/env.default
const envPath = existsSync(resolve(rootDir, 'envs/.env'))
? resolve(rootDir, 'envs/.env')
: existsSync(resolve(monorepoRoot, 'envs/.env'))
? resolve(monorepoRoot, 'envs/.env')
: resolve(monorepoRoot, 'envs/env.default');

const envContent = readFileSync(envPath, 'utf8');
const parsed = dotenv.parse(envContent);
const port = process.env.PLAYGROUND_PORT || parsed.PLAYGROUND_PORT || '6007';

const result = spawnSync('npx', ['storybook', 'dev', '-p', port, '--no-open'], {
cwd: rootDir,
stdio: 'inherit',
shell: true,
env: {
...process.env,
STORYBOOK: 'true',
STORYBOOK_PORT: port,
},
});

process.exit(result.status);
67 changes: 67 additions & 0 deletions adyen-platform-experience-web-vue/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { StorybookConfig } from '@storybook/vue3-vite';
import { fileURLToPath, URL } from 'node:url';
import { getEnvironment } from '../envs/getEnvs.ts';
import { realApiProxies } from '../../endpoints/realApiProxies';

const config: StorybookConfig = {
stories: ['../stories/**/*.stories.*'],
staticDirs: ['../../static'],
framework: {
name: '@storybook/vue3-vite',
options: {},
},
async viteFinal(config) {
const mode = process.env.VITE_MODE ?? 'development';
const { api, app } = getEnvironment(mode);

const removeInspect = (plugins: any[]): any[] =>
plugins.flat(Infinity).filter((p: any) => !(p && typeof p === 'object' && p.name === 'vite-plugin-inspect'));
Comment on lines +17 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using any[] for plugin types bypasses TypeScript's type checking, which can lead to unexpected runtime errors. Consider defining a more specific type for plugins and the individual p elements to leverage TypeScript's benefits fully.

config.plugins = removeInspect(config.plugins ?? []);

config.resolve = {
...config.resolve,
alias: {
...(config.resolve?.alias as Record<string, string>),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Type assertions like as Record<string, string> can be risky if the underlying type is not what's asserted. It's safer to ensure the type is correct through proper typing or handle potential undefined cases explicitly.

'@': fileURLToPath(new URL('../src', import.meta.url)),
},
};

config.server = {
...config.server,
proxy: { ...config.server?.proxy, ...realApiProxies(api, mode) },
};

config.define = {
...config.define,
'process.env.VITE_APP_PORT': JSON.stringify(app.port || null),
'process.env.VITE_APP_URL': JSON.stringify(app.url || null),
'process.env.VITE_APP_LOADING_CONTEXT': JSON.stringify(app.loadingContext || null),
'process.env.VITE_LOCAL_ASSETS': JSON.stringify(process.env.USE_CDN === 'true' ? null : true),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic process.env.USE_CDN === 'true' ? null : true for VITE_LOCAL_ASSETS seems inverted or potentially confusing. If USE_CDN is 'true', VITE_LOCAL_ASSETS becomes null. If USE_CDN is anything else (including undefined or false), VITE_LOCAL_ASSETS becomes true. Please clarify if this is the intended behavior, as it might be more intuitive for VITE_LOCAL_ASSETS to be true when CDN is not used.

'process.env.VITE_VERSION': JSON.stringify('1.0.0'),
'process.env.SESSION_ACCOUNT_HOLDER': JSON.stringify(api.session.accountHolder || null),
'process.env.SESSION_AUTO_REFRESH': JSON.stringify(api.session.autoRefresh === 'true' || null),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the VITE_LOCAL_ASSETS comment, api.session.autoRefresh === 'true' || null results in null if autoRefresh is not the string 'true'. If the intention is to represent a boolean false or undefined when it's not 'true', using null might be inconsistent with boolean expectations. Consider JSON.stringify(api.session.autoRefresh === 'true' ? true : false) or JSON.stringify(api.session.autoRefresh === 'true' || undefined) for clearer boolean representation.

'process.env.SESSION_MAX_AGE_MS': JSON.stringify(api.session.maxAgeMs || null),
'process.env.SESSION_PERMISSIONS': JSON.stringify(api.session.permissions || null),
'process.env.USE_CDN': JSON.stringify(app.useCdn ?? null),
'process.env.VITE_TEST_CDN_ASSETS': JSON.stringify(app.useTestCdn ? true : null),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current check for app.useTestCdn is a truthiness check. Since environment variables are read as strings, a value of 'false' will be treated as true, which is likely not the intended behavior. This could lead to test CDN assets being used incorrectly. To fix this, you should explicitly check if the value is the string 'true'.

Suggested change
'process.env.VITE_TEST_CDN_ASSETS': JSON.stringify(app.useTestCdn ? true : null),
'process.env.VITE_TEST_CDN_ASSETS': JSON.stringify(app.useTestCdn === 'true' ? true : null),

'process.env.NODE_ENV': JSON.stringify(mode),
};

config.build = {
...config.build,
target: 'esnext',
chunkSizeWarningLimit: 800,
rollupOptions: {
...config.build?.rollupOptions,
output: {
...(config.build?.rollupOptions?.output as object),
experimentalMinChunkSize: 10_000,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using as object for config.build?.rollupOptions?.output is a generic type assertion that weakens type safety. It would be better to define a more specific interface or type for the output property to ensure type correctness.

},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The value 10_000 for experimentalMinChunkSize is a magic number. It would improve maintainability and readability to define this as a named constant with a clear explanation of its purpose.

},
};

return config;
},
};

export default config;
7 changes: 7 additions & 0 deletions adyen-platform-experience-web-vue/.storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { addons } from 'storybook/manager-api';

addons.setConfig({
isFullscreen: false,
showPanel: true,
panelPosition: 'bottom',
});
42 changes: 42 additions & 0 deletions adyen-platform-experience-web-vue/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Preview } from '@storybook/vue3';
import { setup } from '@storybook/vue3';
import { createI18n } from 'vue-i18n';
import BentoPlugin from '@adyen/bento-vue3';
import '@adyen/bento-vue3/fonts.css';
import '@adyen/bento-vue3/styles/bento-light';
import '../stories/utils/styles.scss';

const i18n = createI18n({
locale: 'en-US',
fallbackLocale: 'en-US',
fallbackRoot: false,
allowComposition: true,
legacy: false,
messages: {
'en-US': {},
},
});

setup(app => {
app.use(i18n);
app.use(BentoPlugin, { withToast: true, withDesignTokensCSSInjection: false });
});

const preview: Preview = {
parameters: {
controls: {
hideNoControlsWarning: true,
},
},
argTypes: {
locale: {
control: 'select',
options: ['da-DK', 'de-DE', 'en-US', 'es-ES', 'fi-FI', 'fr-FR', 'it-IT', 'nl-NL', 'no-NO', 'pt-BR', 'sv-SE'],
},
},
args: {
locale: 'en-US',
},
};

export default preview;
8 changes: 8 additions & 0 deletions adyen-platform-experience-web-vue/.storybook/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
},
"include": ["./**/*.ts", "../stories/**/*.ts", "../stories/**/*.vue", "../src/**/*.ts", "../src/**/*.vue"]
}
54 changes: 54 additions & 0 deletions adyen-platform-experience-web-vue/endpoints/endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const baseUrl = 'https://platform-components-external-test.adyen.com/platform-components-external/api/v([0-9]+)';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The baseUrl is hardcoded to https://platform-components-external-test.adyen.com. This makes the application less flexible for different environments (e.g., development, staging, production). It's recommended to configure this base URL using environment variables to allow for easy switching between environments without code changes.

const datasetBaseUrl = '/datasets';

export const endpoints = () =>
({
balanceAccounts: `${baseUrl}/balanceAccounts`,
balances: `${baseUrl}/balanceAccounts/:id/balances`,
payouts: `${baseUrl}/payouts`,
payout: `${baseUrl}/payouts/breakdown`,
transactions: `${baseUrl}/transactions`,
transaction: `${baseUrl}/transactions/:id`,
initiateRefund: `${baseUrl}/transactions/:id/refund`,
transactionsTotals: `${baseUrl}/transactions/totals`,
downloadTransactions: `${baseUrl}/transactions/download`,
sessions: '/api/authe/api/v1/sessions',
setup: `${baseUrl}/setup`,
sendEngageEvent: `${baseUrl}/uxdsclient/engage`,
sendTrackEvent: `${baseUrl}/uxdsclient/track`,
reports: `${baseUrl}/reports`,
downloadReport: `${baseUrl}/reports/download`,
stores: `${baseUrl}/stores`,
capital: {
anaCredit: `${baseUrl}/capital/grants/missingActions/anaCredit`,
createOffer: `${baseUrl}/capital/grantOffers/create`,
dynamicOfferConfig: `${baseUrl}/capital/grantOffers/dynamic/configuration`,
dynamicOffer: `${baseUrl}/capital/grantOffers/dynamic`,
grants: `${baseUrl}/capital/grants`,
requestFunds: `${baseUrl}/capital/grants/:id`,
signToS: `${baseUrl}/capital/grants/missingActions/signToS`,
},
disputes: {
accept: `${baseUrl}/disputes/:id/accept`,
defend: `${baseUrl}/disputes/:id/defend`,
details: `${baseUrl}/disputes/:id`,
documents: `${baseUrl}/disputes/:id/documents`,
download: `${baseUrl}/disputes/:id/documents/download`,
list: `${baseUrl}/disputes`,
},
payByLink: {
configuration: `${baseUrl}/paybylink/paymentLinks/:storeId/configuration`,
countries: `${baseUrl}/paybylink/countries`,
currencies: `${baseUrl}/paybylink/currencies`,
details: `${baseUrl}/paybylink/paymentLinks/:id`,
expire: `${baseUrl}/paybylink/paymentLinks/:id/expire`,
filters: `${baseUrl}/paybylink/filters`,
installments: `${baseUrl}/paybylink/installments`,
list: `${baseUrl}/paybylink/paymentLinks`,
settings: `${baseUrl}/paybylink/settings/:storeId`,
themes: `${baseUrl}/paybylink/themes/:id`,
},
datasets: {
countries: `${datasetBaseUrl}/countries/:locale.json?import`,
},
}) as const;
Comment on lines +4 to +54
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The endpoints export is a function that immediately returns a static object. The function wrapper is unnecessary and can be removed for simplicity and to avoid a minor function call overhead. You can export the object directly as a constant.

export const endpoints = {
    balanceAccounts: `${baseUrl}/balanceAccounts`,
    balances: `${baseUrl}/balanceAccounts/:id/balances`,
    payouts: `${baseUrl}/payouts`,
    payout: `${baseUrl}/payouts/breakdown`,
    transactions: `${baseUrl}/transactions`,
    transaction: `${baseUrl}/transactions/:id`,
    initiateRefund: `${baseUrl}/transactions/:id/refund`,
    transactionsTotals: `${baseUrl}/transactions/totals`,
    downloadTransactions: `${baseUrl}/transactions/download`,
    sessions: '/api/authe/api/v1/sessions',
    setup: `${baseUrl}/setup`,
    sendEngageEvent: `${baseUrl}/uxdsclient/engage`,
    sendTrackEvent: `${baseUrl}/uxdsclient/track`,
    reports: `${baseUrl}/reports`,
    downloadReport: `${baseUrl}/reports/download`,
    stores: `${baseUrl}/stores`,
    capital: {
        anaCredit: `${baseUrl}/capital/grants/missingActions/anaCredit`,
        createOffer: `${baseUrl}/capital/grantOffers/create`,
        dynamicOfferConfig: `${baseUrl}/capital/grantOffers/dynamic/configuration`,
        dynamicOffer: `${baseUrl}/capital/grantOffers/dynamic`,
        grants: `${baseUrl}/capital/grants`,
        requestFunds: `${baseUrl}/capital/grants/:id`,
        signToS: `${baseUrl}/capital/grants/missingActions/signToS`,
    },
    disputes: {
        accept: `${baseUrl}/disputes/:id/accept`,
        defend: `${baseUrl}/disputes/:id/defend`,
        details: `${baseUrl}/disputes/:id`,
        documents: `${baseUrl}/disputes/:id/documents`,
        download: `${baseUrl}/disputes/:id/documents/download`,
        list: `${baseUrl}/disputes`,
    },
    payByLink: {
        configuration: `${baseUrl}/paybylink/paymentLinks/:storeId/configuration`,
        countries: `${baseUrl}/paybylink/countries`,
        currencies: `${baseUrl}/paybylink/currencies`,
        details: `${baseUrl}/paybylink/paymentLinks/:id`,
        expire: `${baseUrl}/paybylink/paymentLinks/:id/expire`,
        filters: `${baseUrl}/paybylink/filters`,
        installments: `${baseUrl}/paybylink/installments`,
        list: `${baseUrl}/paybylink/paymentLinks`,
        settings: `${baseUrl}/paybylink/settings/:storeId`,
        themes: `${baseUrl}/paybylink/themes/:id`,
    },
    datasets: {
        countries: `${datasetBaseUrl}/countries/:locale.json?import`,
    },
} as const;

44 changes: 44 additions & 0 deletions adyen-platform-experience-web-vue/envs/getEnvs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { dirname, resolve } from 'path';
import { loadEnv } from 'vite';
import { fileURLToPath } from 'url';

const filename = fileURLToPath(import.meta.url);

// Point to project root (parent of envs/ directory)
const envDir = resolve(dirname(filename), '..');

const parseEnv = (env: Record<string, string | undefined>) => {
// Interpolate shell-style variables like $PLAYGROUND_HOST:$PLAYGROUND_PORT
const interpolate = (value: string | undefined): string | undefined => {
if (!value) return value;
return value.replace(/\$PLAYGROUND_HOST/g, env.PLAYGROUND_HOST ?? 'localhost').replace(/\$PLAYGROUND_PORT/g, env.PLAYGROUND_PORT ?? '5173');
};

return {
api: {
session: {
accountHolder: env.SESSION_ACCOUNT_HOLDER,
apiKey: env.API_KEY,
autoRefresh: env.SESSION_AUTO_REFRESH,
maxAgeMs: env.SESSION_MAX_AGE_MS,
permissions: env.SESSION_PERMISSIONS,
url: env.SESSION_API_URL ?? '',
},
},
app: {
host: env.PLAYGROUND_HOST ?? 'localhost',
port: parseInt(env.PLAYGROUND_PORT ?? '5173'),
loadingContext: env.LOADING_CONTEXT,
url: interpolate(env.PLAYGROUND_URL) ?? `http://${env.PLAYGROUND_HOST ?? 'localhost'}:${env.PLAYGROUND_PORT ?? '5173'}/`,
useCdn: env.USE_CDN,
useTestCdn: env.USE_TEST_CDN,
},
};
};

export type Environment = ReturnType<typeof parseEnv>;

export const getEnvironment = (mode: string): Environment => {
const envVars = { ...process.env, ...loadEnv(mode, envDir, '') };
return parseEnv(envVars);
};
8 changes: 8 additions & 0 deletions adyen-platform-experience-web-vue/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}
54 changes: 54 additions & 0 deletions adyen-platform-experience-web-vue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@adyen/adyen-platform-experience-web-vue",
"version": "0.0.0",
"private": false,
"type": "module",
"main": "./dist/es/index.js",
"module": "./dist/es/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/es/index.js"
},
"./style.css": "./dist/adyen-platform-experience-web-vue.css"
},
"sideEffects": [
"**/*.css"
],
"files": [
"dist"
],
"scripts": {
"start": "pnpm run storybook",
"build": "vite build && vue-tsc -p tsconfig.build.json",
"preview": "vite preview",
"storybook": "node .storybook/dev.js",
"storybook:build": "storybook build",
"test": "cross-env TEST_ENV=1 vitest --config vite.config.ts"
},
"dependencies": {
"@adyen/bento-vue3": "^1.86.0",
"vue-i18n": "^11"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The vue-i18n dependency is specified with a wide version range (^11). While this allows for minor updates, it could potentially pull in versions with breaking changes if a new major version (e.g., 12.0.0) is released that still satisfies ^11 but introduces breaking changes. Consider pinning to a specific major version or using a more restrictive range if stability is critical.

},
"peerDependencies": {
"vue": "^3.3.0"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The vue peer dependency is specified with a wide version range (^3.3.0). While this allows for minor updates, it could potentially pull in versions with breaking changes if a new major version (e.g., 4.0.0) is released that still satisfies ^3.3.0 but introduces breaking changes. Consider pinning to a specific major version or using a more restrictive range if stability is critical.

},
"devDependencies": {
"@storybook/vue3": "^10.1.10",
Comment on lines +40 to +42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Storybook dependencies (@storybook/vue3, @storybook/vue3-vite, storybook) are using version ^10.1.10. Storybook has since released major version 8. It's advisable to update these dependencies to the latest stable version (Storybook 8.x) to benefit from new features, performance improvements, and bug fixes. This might involve some migration effort.

"@storybook/vue3-vite": "^10.1.10",
"@types/node": "^24.10.13",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The @types/node dependency is pinned to version ^24.10.13, which corresponds to an unstable/nightly release of Node.js. To ensure stability and prevent potential compatibility issues for developers using LTS versions of Node, it's recommended to use types for a stable LTS release, such as Node 20.

Suggested change
"@types/node": "^24.10.13",
"@types/node": "^20.0.0",

"@vitejs/plugin-vue": "^6.0.3",
"cross-env": "^10.1.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The cross-env dependency is outdated (^10.1.0). The latest stable version is 7.0.3. Updating this dependency is recommended to ensure compatibility and benefit from any improvements or bug fixes.

"dotenv": "^16.4.7",
"sass": "^1.77.6",
"storybook": "^10.1.10",
"terser": "^5.46.0",
"typescript": "^5.8.3",
"vite": "^7.3.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The vite dependency is outdated (^7.3.0). The latest stable version is 5.x. Updating Vite is highly recommended for performance improvements, new features, and better compatibility with modern web development practices. This might require reviewing the Vite configuration for any breaking changes.

"vite-plugin-vue-devtools": "^8.0.5",
"vitest": "^4.0.18",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The vitest dependency is outdated (^4.0.18). The latest stable version is 1.x. Updating Vitest will provide access to the latest testing features, performance enhancements, and bug fixes. This might require reviewing test configurations for any breaking changes.

"vue": "^3.5.26",
"vue-tsc": "^2.2.4"
}
}
Loading
Loading