diff --git a/.gitignore b/.gitignore
index 3cd4ffe..5e35be5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,7 @@ npm-debug.log
yarn-error.log
testem.log
/typings
+apps/wordpress-plugin/fcc-donation/assets/
# System Files
.DS_Store
diff --git a/apps/frontend/project.json b/apps/frontend/project.json
index 40241a1..fc27e01 100644
--- a/apps/frontend/project.json
+++ b/apps/frontend/project.json
@@ -75,6 +75,14 @@
"buildTarget": "frontend:build"
}
},
+ "build-embed": {
+ "executor": "@nx/vite:build",
+ "outputs": ["{workspaceRoot}/apps/wordpress-plugin/fcc-donation/assets"],
+ "options": {
+ "outputPath": "apps/wordpress-plugin/fcc-donation/assets",
+ "configFile": "apps/frontend/vite.embed.config.mts"
+ }
+ },
"typecheck": {
"executor": "nx:run-commands",
"options": {
diff --git a/apps/frontend/src/components/testimonials/testimonials.css b/apps/frontend/src/components/testimonials/testimonials.css
index e7578e5..c2f2821 100644
--- a/apps/frontend/src/components/testimonials/testimonials.css
+++ b/apps/frontend/src/components/testimonials/testimonials.css
@@ -1,6 +1,7 @@
/* Outer card */
.testimonial-carousel {
- width: 1200px;
+ width: 100%;
+ max-width: 1200px;
height: 395px;
border-radius: 10px;
overflow: hidden;
@@ -67,8 +68,8 @@
font-size: 20px;
font-weight: 500;
line-height: 100%;
- color: #ffffff;
- text-decoration-line: underline;
+ color: #ffffff !important;
+ text-decoration-line: underline !important;
text-decoration-style: solid;
text-decoration-skip-ink: auto;
text-decoration-color: rgba(255, 255, 255, 0.58);
diff --git a/apps/frontend/src/containers/donations/donations.css b/apps/frontend/src/containers/donations/donations.css
index be5f6e2..5e7c55b 100644
--- a/apps/frontend/src/containers/donations/donations.css
+++ b/apps/frontend/src/containers/donations/donations.css
@@ -13,6 +13,14 @@
text-align: center;
}
+#fcc-donation-embed input#amount {
+ padding-left: 1.5rem !important;
+}
+
+#fcc-donation-embed button {
+ min-width: 0 !important;
+}
+
.form-group {
display: flex;
flex-direction: column;
@@ -328,7 +336,7 @@
width: 31%;
aspect-ratio: 14 / 1;
border-radius: 10px;
- background: #3D3E6E;
+ background: #3d3e6e;
}
.step2-container {
diff --git a/apps/frontend/src/containers/donations/steps/DonationAmount.tsx b/apps/frontend/src/containers/donations/steps/DonationAmount.tsx
index d4046ab..d06e162 100644
--- a/apps/frontend/src/containers/donations/steps/DonationAmount.tsx
+++ b/apps/frontend/src/containers/donations/steps/DonationAmount.tsx
@@ -71,7 +71,7 @@ export const DonationAmount = ({
onChange={onChange}
onBlur={onAmountBlur}
className={cn(
- 'pl-7 pr-12 h-10 text-base font-normal border-[#4E4E4E] border-[1.5px] shadow-none focus-visible:ring-0 rounded-lg',
+ 'pl-8 pr-12 h-10 text-base font-normal border-[#4E4E4E] border-[1.5px] shadow-none focus-visible:ring-0 rounded-lg',
error ? 'border-[#d93025] bg-[#fff6f6]' : '',
)}
disabled={isSubmitting}
diff --git a/apps/frontend/src/containers/donations/steps/DonationRecurrence.tsx b/apps/frontend/src/containers/donations/steps/DonationRecurrence.tsx
index 507c40a..441a8b9 100644
--- a/apps/frontend/src/containers/donations/steps/DonationRecurrence.tsx
+++ b/apps/frontend/src/containers/donations/steps/DonationRecurrence.tsx
@@ -33,7 +33,7 @@ export const DonationRecurrence = ({
key={option.label}
type="button"
className={cn(
- 'flex-1 h-12 px-4 whitespace-nowrap rounded-lg border-[1.5px] text-base cursor-pointer transition-colors duration-150 ease-in-out font-semibold disabled:opacity-60 disabled:cursor-not-allowed',
+ 'flex-1 h-12 px-4 rounded-lg border-[1.5px] text-base cursor-pointer transition-colors duration-150 ease-in-out font-semibold disabled:opacity-60 disabled:cursor-not-allowed',
isRecurrenceSelected(option)
? 'bg-[#007b64] text-white border-[#007b64]'
: 'bg-white text-black border-[#4E4E4E]',
diff --git a/apps/frontend/src/containers/root.css b/apps/frontend/src/containers/root.css
index 6785bb3..581e1fd 100644
--- a/apps/frontend/src/containers/root.css
+++ b/apps/frontend/src/containers/root.css
@@ -3,7 +3,7 @@
display: flex;
justify-content: center;
background: #ffffff;
- padding: 3rem 1rem 4rem;
+ padding: 3rem 1rem 6rem;
}
.root-container {
diff --git a/apps/frontend/src/embed.tsx b/apps/frontend/src/embed.tsx
new file mode 100644
index 0000000..6d24909
--- /dev/null
+++ b/apps/frontend/src/embed.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { MemoryRouter } from 'react-router-dom';
+import { DonationForm } from './containers/donations/DonationForm';
+import {
+ GrowingGoal,
+ type SampleDonation,
+} from '@components/GrowingGoal/GrowingGoal';
+import { TestimonialCarousel } from '@components/testimonials/TestimonialCarousel';
+import { useActiveGoal } from './hooks/useActiveGoal';
+import './containers/root.css';
+import './styles.css';
+
+const SAMPLE_DONATION: SampleDonation = {
+ name: 'C4C',
+ amount: 500,
+ profile:
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAALElEQVR42mNgGAWjgLGB4T8DGjBgFiMDw38GphCjEopFY1GNRg0Y1GgAAAD9YB5WfVii1AAAAABJRU5ErkJggg==',
+};
+
+const EmbedApp: React.FC = () => {
+ const { data } = useActiveGoal();
+ const donationTotal = data?.amountRaised ?? 3000;
+ const targetGoal = data?.goal?.targetAmount ?? 10000;
+ const label = data?.goal?.title || 'Grow your community with FCC';
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ console.log('[FCC Donation] Submitted:', id)}
+ onError={(err) => console.error('[FCC Donation] Error:', err)}
+ />
+
+
+
+
+ );
+};
+
+const mountId = 'fcc-donation-embed';
+const container = document.getElementById(mountId);
+
+if (container) {
+ ReactDOM.createRoot(container).render(
+
+
+
+
+ ,
+ );
+} else {
+ console.warn(`[FCC Donation] Mount element #${mountId} not found.`);
+}
diff --git a/apps/frontend/vite.embed.config.mts b/apps/frontend/vite.embed.config.mts
new file mode 100644
index 0000000..589d3b9
--- /dev/null
+++ b/apps/frontend/vite.embed.config.mts
@@ -0,0 +1,44 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
+import path from 'path';
+
+export default defineConfig({
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/frontend-embed',
+
+ plugins: [react(), nxViteTsPaths()],
+ publicDir: false,
+
+ define: {
+ // Allow WordPress pages to set window.__FCC_DONATION_API_URL__ before loading
+ // the script to point at the backend (e.g. 'https://api.example.com').
+ // Falls back to same-origin via the ?? '' in apiClient.ts if not set.
+ 'import.meta.env.VITE_API_BASE_URL': 'window.__FCC_DONATION_API_URL__',
+ // Required in IIFE lib mode — React and other deps reference this Node global.
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+
+ build: {
+ lib: {
+ entry: path.resolve(__dirname, 'src/embed.tsx'),
+ name: 'FCCDonation',
+ fileName: 'fcc-donation',
+ formats: ['iife'],
+ },
+ outDir: '../../apps/wordpress-plugin/fcc-donation/assets',
+ emptyOutDir: true,
+ },
+
+ resolve: {
+ alias: {
+ '@api': path.resolve(__dirname, './src/api'),
+ '@components': path.resolve(__dirname, './src/components'),
+ '@containers': path.resolve(__dirname, './src/containers'),
+ '@lib': path.resolve(__dirname, './src/lib'),
+ '@public': path.resolve(__dirname, './public'),
+ '@shared': path.resolve(__dirname, '../../shared'),
+ '@utils': path.resolve(__dirname, './src/utils'),
+ },
+ },
+});
diff --git a/apps/wordpress-plugin/fcc-donation/README.md b/apps/wordpress-plugin/fcc-donation/README.md
new file mode 100644
index 0000000..7925ca0
--- /dev/null
+++ b/apps/wordpress-plugin/fcc-donation/README.md
@@ -0,0 +1,52 @@
+# FCC Donation Embed — WordPress Plugin
+
+Embeds the FCC donation form and growing goal widget into any WordPress page via a shortcode.
+
+## How the shortcode works
+
+Add `[fcc_donation]` to any WordPress page or post body.
+
+When the page renders, WordPress replaces the shortcode with a `` mount point and automatically enqueues the JS and CSS — only on pages where the shortcode is present, not sitewide.
+
+### What gets rendered
+
+The shortcode renders the same layout as the main app's home page:
+- "Make a Difference" banner at the top
+- Growing goal widget and donation form side by side below
+
+The growing goal data (amount raised, target, title) is fetched live from the backend's `/api/donations/goal/active` endpoint on page load.
+
+---
+
+## Where assets live
+
+```
+apps/wordpress-plugin/fcc-donation/
+├── fcc-donation.php # Plugin registration and shortcode handler
+├── README.md # This file
+└── assets/ # Built output — regenerated by nx build-embed frontend
+ ├── fcc-donation.iife.js
+ └── fcc-donation.css
+```
+
+The `assets/` folder is excluded from git (see `.gitignore`) because it is always regenerated from source. The PHP file is the only thing in this directory that is version-controlled.
+
+---
+
+## How to update the build
+
+After making changes to any frontend code (donation form, growing goal, styles, etc.), rebuild the embed bundle from the repo root:
+
+```bash
+nx build-embed frontend
+```
+
+This outputs updated files to `apps/wordpress-plugin/fcc-donation/assets/`. Copy the new file contents into WordPress using the Plugin File Editor:
+
+1. In the WordPress admin, go to **Plugins → Plugin File Editor**.
+2. In the top-right dropdown labeled **"Select plugin to edit:"**, choose **FCC Donation Embed** and click **Select**.
+3. Expand the `assets/` folder in the file tree.
+4. Click **fcc-donation.css** and replace its contents with those of the newly-generated `fcc-donation.css`.
+5. Click **fcc-donation.iife.js** and replace its contents with those of the newly-generated `fcc-donation.iife.js`.
+
+No changes to the PHP file or WordPress plugin settings are needed unless the shortcode interface itself changes.
\ No newline at end of file
diff --git a/apps/wordpress-plugin/fcc-donation/fcc-donation.php b/apps/wordpress-plugin/fcc-donation/fcc-donation.php
new file mode 100644
index 0000000..a62aae4
--- /dev/null
+++ b/apps/wordpress-plugin/fcc-donation/fcc-donation.php
@@ -0,0 +1,51 @@
+ '' ],
+ $atts,
+ 'fcc_donation'
+ );
+
+ wp_enqueue_style(
+ 'fcc-donation',
+ plugins_url( 'assets/fcc-donation.css', __FILE__ ),
+ [],
+ '1.0.0'
+ );
+
+ wp_enqueue_script(
+ 'fcc-donation',
+ plugins_url( 'assets/fcc-donation.iife.js', __FILE__ ),
+ [],
+ '1.0.0',
+ true // load in footer so the DOM is ready
+ );
+
+ if ( ! empty( $atts['api_url'] ) ) {
+ wp_add_inline_script(
+ 'fcc-donation',
+ 'window.__FCC_DONATION_API_URL__ = ' . wp_json_encode( $atts['api_url'] ) . ';',
+ 'before'
+ );
+ }
+
+ return '
';
+}
+
+add_shortcode( 'fcc_donation', 'fcc_donation_shortcode' );
diff --git a/fcc-donation.zip b/fcc-donation.zip
new file mode 100644
index 0000000..ec3e404
Binary files /dev/null and b/fcc-donation.zip differ