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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ composer.lock
# Build artifacts
/build/dist/
.phpunit.result.cache
/dist/
/frontend/node_modules/
19 changes: 19 additions & 0 deletions frontend/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import SettingsPage from './components/SettingsPage.jsx';

export default function App() {
const {
nonce = '',
actionUrl = '',
restUrl = '',
restNonce = ''
} = window.xpubSettings || {};

return (
<SettingsPage
nonce={nonce}
actionUrl={actionUrl}
restUrl={restUrl}
restNonce={restNonce}
/>
);
}
28 changes: 28 additions & 0 deletions frontend/components/SettingsPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useEffect} from 'react';

export default function SettingsPage({
nonce = '',
actionUrl = '',
restUrl = '',
restNonce = ''
}) {
useEffect(() => {
const base = restUrl.replace(/\/$/, '');

// Example REST request
fetch(`${base}/xpub/v1/example`, {
headers: { 'X-WP-Nonce': restNonce },
});

// Example AJAX request
const formData = new FormData();
formData.append('action', 'xpub_dummy_action');
formData.append('_wpnonce', nonce);
fetch(actionUrl, {
method: 'POST',
body: formData,
});
}, [nonce, actionUrl, restUrl, restNonce]);

return <div>Dummy Settings Page</div>;
}
23 changes: 23 additions & 0 deletions frontend/i18n-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {setLocaleData} from '@wordpress/i18n';
import './main.jsx';

const locale = window?.xpubSettings?.locale || 'en_US';
const domain = 'xpub-multi-channel-publisher';
const baseUrl = window?.xpubSettings?.translationsBaseUrl || '';

(async () => {
try {
const response = await fetch(`${baseUrl}/${locale}.json`);
if (!response.ok) throw new Error(`Translation for ${locale} not found`);
const json = await response.json();

const messages = json?.locale_data?.messages;
if (messages) {
setLocaleData(messages, domain);
} else {
console.warn('No locale_data.messages found!');
}
} catch (err) {
console.warn(`Could not load translation for locale "${locale}".`, err);
}
})();
3 changes: 3 additions & 0 deletions frontend/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
12 changes: 12 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XPUB Settings</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/main.jsx"></script>
</body>
</html>
18 changes: 18 additions & 0 deletions frontend/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.jsx';
import './index.css';

document.addEventListener('DOMContentLoaded', () => {
const rootElement = document.getElementById('xpub-settings-root');
if (rootElement) {
const root = createRoot(rootElement);
root.render(
<React.StrictMode>
<App/>
</React.StrictMode>
);
} else {
console.error('React root element not found');
}
});
23 changes: 23 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "wp-skeleton-frontend",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@wordpress/i18n": "^6.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/preset-react": "^7.27.1",
"@vitejs/plugin-react": "^4.0.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0",
"tailwindcss": "^3.3.0",
"vite": "^7.0.6"
}
}
1 change: 1 addition & 0 deletions frontend/translations/de_DE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"translation-revision-date":"","generator":"WP-CLI\/2.12.0","source":"resources\/views\/admin\/SettingsPage.jsx","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de_DE","plural-forms":"nplurals=2; plural=(n != 1);"},"Activate Publisher":["Publisher aktivieren"],"Select active publishers:":["W\u00e4hle aktive Publisher:"],"Configuration":["Konfiguration"],"Save settings":["Einstellungen speichern"],"api_key":["API-Schl\u00fcssel"],"client_id":["Client-ID"],"Custom Text for Social Sharing":["Individueller Text f\u00fcr Social Media"],"Changes saved successfully.":["\u00c4nderungen erfolgreich gespeichert."],"Saving...":["Wird gespeichert..."],"Publisher settings":["Publisher-Einstellungen"],"OAuth":["OAuth"],"Settings":["Einstellungen"],"Authenticate with":["Authentifizieren mit"],"Connected":["Verbunden"],"No redirect URL received.":["Keine Weiterleitungs-URL erhalten."],"OAuth start failed.":["OAuth-Start fehlgeschlagen."],"OAuth successful!":["OAuth erfolgreich!"]}}}
1 change: 1 addition & 0 deletions frontend/translations/fr_FR.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"translation-revision-date":"","generator":"WP-CLI\/2.12.0","source":"resources\/views\/admin\/SettingsPage.jsx","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"fr_FR","plural-forms":"nplurals=2; plural=(n > 1);"},"Activate Publisher":["Activer l\u2019\u00e9diteur"],"Select active publishers:":["S\u00e9lectionner les \u00e9diteurs actifs\u00a0:"],"Configuration":["Configuration"],"Save settings":["Enregistrer les param\u00e8tres"],"api_key":["Cl\u00e9 API"],"client_id":["ID client"],"Custom Text for Social Sharing":["Texte personnalis\u00e9 pour le partage social"],"OAuth":["OAuth"],"Settings":["Param\u00e8tres"],"Authenticate with":["S\u2019authentifier avec"],"Connected":["Connect\u00e9"],"No redirect URL received.":["Aucune URL de redirection re\u00e7ue."],"OAuth start failed.":["\u00c9chec du d\u00e9marrage OAuth."],"OAuth successful!":["OAuth r\u00e9ussi !"]}}}
1 change: 1 addition & 0 deletions frontend/translations/it_IT.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"translation-revision-date":"","generator":"WP-CLI\/2.12.0","source":"resources\/views\/admin\/SettingsPage.jsx","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"it_IT","plural-forms":"nplurals=2; plural=(n != 1);"},"Activate Publisher":["Attiva l\u2019editore"],"Select active publishers:":["Seleziona gli editori attivi:"],"Configuration":["Configurazione"],"Save settings":["Salva impostazioni"],"api_key":["Chiave API"],"client_id":["ID client"],"Custom Text for Social Sharing":["Testo personalizzato per la condivisione sui social"],"OAuth":["OAuth"],"Settings":["Impostazioni"],"Authenticate with":["Autenticati con"],"Connected":["Connesso"],"No redirect URL received.":["Nessun URL di reindirizzamento ricevuto."],"OAuth start failed.":["Avvio OAuth non riuscito."],"OAuth successful!":["OAuth completato con successo!"]}}}
1 change: 1 addition & 0 deletions frontend/translations/ru_RU.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"translation-revision-date":"","generator":"WP-CLI\/2.12.0","source":"resources\/views\/admin\/SettingsPage.jsx","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"ru_RU","plural-forms":"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"},"Activate Publisher":["\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u0434\u0430\u0442\u0435\u043b\u044f"],"Select active publishers:":["\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0438\u0437\u0434\u0430\u0442\u0435\u043b\u0435\u0439:"],"Configuration":["\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f"],"Save settings":["\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438"],"api_key":["\u041a\u043b\u044e\u0447 API"],"client_id":["ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430"],"Custom Text for Social Sharing":["\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0432 \u0441\u043e\u0446\u0441\u0435\u0442\u044f\u0445"],"OAuth":["OAuth"],"Settings":["\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438"],"Authenticate with":["\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437"],"Connected":["\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e"],"No redirect URL received.":["URL \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d."],"OAuth start failed.":["\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 OAuth."],"OAuth successful!":["OAuth \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d!"]}}}
39 changes: 39 additions & 0 deletions frontend/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {defineConfig} from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [
react(),
],
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
build: {
manifest: true,
outDir: '../dist',
emptyOutDir: true,
cssCodeSplit: true,
rollupOptions: {
input: {
app: './i18n-loader.js',
},
output: {
format: 'es',
entryFileNames: '[name].js',
assetFileNames: '[name].[ext]',
chunkFileNames: '[name].js',
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'@wordpress/i18n': 'wp.i18n',
},
},
},
},
assetsInclude: ['**/*.json'],
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
},
});
36 changes: 36 additions & 0 deletions src/Infrastructure/WordPress/Admin/SettingsPageRegistrar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace WP\Skeleton\Infrastructure\WordPress\Admin;

use WP\Skeleton\Infrastructure\WordPress\Hook\HookRegistrableInterface;
use WP\Skeleton\Infrastructure\WordPress\React\Components\XPubSettingsAppLoader;

final class SettingsPageRegistrar implements HookRegistrableInterface
{
public function __construct(
private XPubSettingsAppLoader $appLoader,
) {
}

public function register(): void
{
// Skeleton: register hooks for adding and displaying the settings page.
}

public function addOptionsPage(): void
{
// Skeleton: add the options page to WordPress.
}

public function renderSettingsPage(): void
{
// Skeleton: render the settings page content.
}

public function enqueueAssets(string $hook): void
{
// Skeleton: enqueue scripts for the settings page.
}
}
10 changes: 10 additions & 0 deletions src/Infrastructure/WordPress/Hook/HookRegistrableInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace WP\Skeleton\Infrastructure\WordPress\Hook;

interface HookRegistrableInterface
{
public function register(): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace WP\Skeleton\Infrastructure\WordPress\React\Components;

use WP\Skeleton\Infrastructure\WordPress\React\ReactAppLoader;
use WP\Skeleton\Shared\Plugin\PluginContext;

final class XPubSettingsAppLoader
{
public function __construct(
private ReactAppLoader $appLoader,
private PluginContext $pluginContext,
) {
}

public function register(): void
{
$data = [
'nonce' => '',
'actionUrl' => '',
'restUrl' => '',
'restNonce' => '',
];

// Skeleton: enqueue scripts and inject settings data for the React app.
$this->appLoader->load('main.jsx', 'xpubSettings', $data);
}
}
20 changes: 20 additions & 0 deletions src/Infrastructure/WordPress/React/ReactAppLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace WP\Skeleton\Infrastructure\WordPress\React;

use WP\Skeleton\Shared\Plugin\PluginContext;

final class ReactAppLoader
{
public function __construct(
private PluginContext $pluginContext,
) {
}

public function load(string $scriptName, string $jsVarName, array $dataToInject): void
{
// Skeleton: implement asset loading for the React application.
}
}
Loading