Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue-97: Remove dependency on Fieldmanager and manually create admin pages #121

Draft
wants to merge 13 commits into
base: develop
Choose a base branch
from
Draft
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
27 changes: 15 additions & 12 deletions blocks/footer/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { Spinner } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';

interface FooterSettings {
interface Settings {
from_email: string,
reply_to_email: string,
from_names: string[],
facebook_url: string,
twitter_url: string,
instagram_url: string,
Expand All @@ -38,22 +41,22 @@ interface FooterSettings {
*/
export default function Edit() {
const [isLoading, setIsLoading] = useState(false);
const [footerSettings, setFooterSettings] = useState<FooterSettings>();
const [settings, setSettings] = useState<Settings>();

const facebookUrl = footerSettings?.facebook_url ?? '';
const twitterUrl = footerSettings?.twitter_url ?? '';
const instagramUrl = footerSettings?.instagram_url ?? '';
const youtubeUrl = footerSettings?.youtube_url ?? '';
const imageId = footerSettings?.image ?? 0;
const address = footerSettings?.address ?? '';
const address2 = footerSettings?.address_2 ?? '';
const facebookUrl = settings?.facebook_url ?? '';
const twitterUrl = settings?.twitter_url ?? '';
const instagramUrl = settings?.instagram_url ?? '';
const youtubeUrl = settings?.youtube_url ?? '';
const imageId = settings?.image ?? 0;
const address = settings?.address ?? '';
const address2 = settings?.address_2 ?? '';

useEffect(() => {
setIsLoading(true);

apiFetch({ path: '/wp-newsletter-builder/v1/footer_settings' })
apiFetch({ path: '/wp-newsletter-builder/v1/settings' })
.then((response) => {
setFooterSettings(response as any as FooterSettings);
setSettings(response as any as Settings);
Copy link
Member

Choose a reason for hiding this comment

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

I recognize that you didn't change the underlying code here, but it would be good to validate that what comes back from the API is in the proper shape. What I usually do here is create a function that takes an unknown parameter and guarantees a return type, and throws if it can't. The zod library is very good for this and I can help point you in the right direction for how to implement it (it's pretty straightforward but I have examples I can share).

setIsLoading(false);
});
}, []);
Expand All @@ -63,7 +66,7 @@ export default function Edit() {
} = useSelect((select) => ({
// @ts-ignore
media: imageId ? select('core').getMedia(imageId) : null,
}), [footerSettings, imageId]);
}), [settings, imageId]);

const imageUrl = media ? media.source_url : '';
const imageAltText = media ? media.alt_text : '';
Expand Down
15 changes: 7 additions & 8 deletions blocks/footer/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
*/

$nb_settings = get_option( 'nb_settings' );
$nb_footer_settings = is_array( $nb_settings ) ? $nb_settings['footer_settings'] : [];
$nb_facebook_url = $nb_footer_settings['facebook_url'] ?? '';
$nb_twitter_url = $nb_footer_settings['twitter_url'] ?? '';
$nb_instagram_url = $nb_footer_settings['instagram_url'] ?? '';
$nb_youtube_url = $nb_footer_settings['youtube_url'] ?? '';
$nb_image_id = $nb_footer_settings['image'] ?? 0;
$nb_address = $nb_footer_settings['address'] ?? '';
$nb_address_2 = $nb_footer_settings['address_2'] ?? '';
$nb_facebook_url = $nb_settings['facebook_url'] ?? '';
$nb_twitter_url = $nb_settings['twitter_url'] ?? '';
$nb_instagram_url = $nb_settings['instagram_url'] ?? '';
$nb_youtube_url = $nb_settings['youtube_url'] ?? '';
$nb_image_id = $nb_settings['image'] ?? 0;
$nb_address = $nb_settings['address'] ?? '';
$nb_address_2 = $nb_settings['address_2'] ?? '';
$nb_has_social_links = ! empty( $nb_facebook_url ) || ! empty( $nb_twitter_url ) || ! empty( $nb_instagram_url ) || ! empty( $nb_youtube_url );

$plugin_url = plugins_url( 'wp-newsletter-builder' );
Expand Down
11 changes: 11 additions & 0 deletions components/imagePicker/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.image-picker {
&__button-group {
display: flex;
gap: 5px;
margin-bottom: 5px;
}

&__preview {
max-height: 200px;
}
}
162 changes: 162 additions & 0 deletions components/imagePicker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/* eslint-disable camelcase */
import { __ } from '@wordpress/i18n';
import { BaseControl, Button, ButtonGroup } from '@wordpress/components';
import { useEffect, useRef, useState } from 'react';
import apiFetch from '@wordpress/api-fetch';
import type { WP_REST_API_Attachment } from 'wp-types';
import './index.scss';

type ImagePickerProps = {
label: string;
onChange: (value: number) => void;
value: number;
};

type MediaLibraryOptions = {
library: {
type: string;
};
};

type MediaLibrarySelection = {
id: number;
url: string;
};

declare global {
interface Window {
wp: {
media: (options: MediaLibraryOptions) => any;
};
}
}

export default function ImagePicker({
Copy link
Member

Choose a reason for hiding this comment

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

We have an image picker component available as part of block-editor-tools which I would like to use here if possible: https://github.com/alleyinteractive/alley-scripts/tree/main/packages/block-editor-tools/src/components/image-picker

label,
onChange,
value,
}: ImagePickerProps) {
const imagePreviewRef = useRef<HTMLImageElement>(null);
const imagePreview = imagePreviewRef.current as HTMLImageElement;
const [imageUrl, setImageUrl] = useState('');

/**
* Handle the Media Library modal logic.
* @returns Promise
*/
const openMediaLibraryModal = () => new Promise((resolve, reject) => {
// Create the Media Library object. Restrict to images only.
const mediaLibrary = window?.wp?.media({
library: {
type: 'image',
},
});

if (!mediaLibrary) {
reject();
}

// Set up the select event listener. On success, returns a promise with the image ID and URL.
mediaLibrary?.on('select', () => {
const selectedImage = mediaLibrary?.state()?.get('selection')?.first();

if (!selectedImage) {
reject();
}

const {
attributes: {
id = 0,
url = '',
} = {},
} = selectedImage;

resolve({ id, url });
});

// Open the Media Library modal.
mediaLibrary?.open();
});

/**
* Select an image.
*/
const selectImage = async () => {
const imageData = await openMediaLibraryModal() as MediaLibrarySelection;

const {
id = 0,
url = '',
} = imageData;

// Pass the selected attachment ID to the onChange event.
onChange(id);

// Update the image URL state.
setImageUrl(url);
};

/**
* Clear the selected image.
*/
const clearImage = () => {
onChange(0);
setImageUrl('');
};

/**
* Fetch the image URL from the REST API when the image ID changes.
*/
useEffect(() => {
if (!value) {
return;
}

// Get the image url from the REST API and update the image preview.
apiFetch({ path: `/wp/v2/media/${value}` })
.then((response) => {
const { source_url: url = '' } = response as WP_REST_API_Attachment;
setImageUrl(url);
})
.catch(() => {
setImageUrl('');
});
}, [value]);

/**
* Update the image preview when the image URL changes.
*/
useEffect(() => {
if (!imageUrl || !imagePreview) {
return;
}

imagePreview.src = imageUrl;
}, [imageUrl, imagePreview]);

return (
<BaseControl label={label}>
<ButtonGroup className="image-picker__button-group">
<Button
onClick={selectImage}
variant="secondary"
>
{__('Select an Image', 'wp-newsletter-builder')}
</Button>
<Button
disabled={!imageUrl}
onClick={clearImage}
variant="secondary"
>
{__('Clear Image', 'wp-newsletter-builder')}
</Button>
</ButtonGroup>
<img
alt=""
className="image-picker__preview"
ref={imagePreviewRef}
src={imageUrl}
/>
</BaseControl>
);
}
3 changes: 3 additions & 0 deletions entries/admin-settings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import '@/scss/admin-settings/notice.scss';
import '@/scss/admin-settings/sortable-item.scss';
import '@/scss/admin-settings/wrapper-group.scss';
19 changes: 19 additions & 0 deletions plugins/admin-settings/email-types/frontend.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

// Components.
import AdminEmailTypes from './index';

const element = document.getElementById('wp-newsletter-builder-settings__page-email-types');

if (element) {
const root = createRoot(element);

if (root) {
root.render(
<StrictMode>
<AdminEmailTypes />
</StrictMode>,
);
}
}
Loading
Loading