aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Lee <alee14498@protonmail.com>2024-06-29 08:38:44 -0400
committerAndrew Lee <alee14498@protonmail.com>2024-06-29 08:38:44 -0400
commitbeb54b0acb7721068bc36da6906bd615eb01fb8c (patch)
tree2a5b92b3273a3ac8376bbd54f91f28721ca82c6e /src
parentfd1d608df8544873f8f8325e0507db1c59723549 (diff)
downloadpersonal-website-beb54b0acb7721068bc36da6906bd615eb01fb8c.tar.gz
personal-website-beb54b0acb7721068bc36da6906bd615eb01fb8c.tar.bz2
personal-website-beb54b0acb7721068bc36da6906bd615eb01fb8c.zip
Merged Guestbook and Blog Comments into HOC
Diffstat (limited to 'src')
-rw-r--r--src/components/BlogComments.jsx107
-rw-r--r--src/components/BlogCommentsForm.jsx150
-rw-r--r--src/components/FetchMessages.jsx86
-rw-r--r--src/components/FormHandling.jsx111
-rw-r--r--src/components/Guestbook.jsx113
-rw-r--r--src/components/GuestbookForm.jsx157
6 files changed, 303 insertions, 421 deletions
diff --git a/src/components/BlogComments.jsx b/src/components/BlogComments.jsx
index 183fb94..9914741 100644
--- a/src/components/BlogComments.jsx
+++ b/src/components/BlogComments.jsx
@@ -1,89 +1,20 @@
-import { Component } from 'preact';
-import { formatDate } from "../util";
-import sanitizeHtml from 'sanitize-html';
+import withMessages from './FetchMessages';
import BlogCommentsForm from "./BlogCommentsForm.jsx";
-
-class BlogComments extends Component {
- state = {
- message: null,
- error: null,
- page: 1,
- };
-
- fetchMessages = async (page) => {
- try {
- const response = await fetch(`${import.meta.env.PUBLIC_API_URL}/comments/${this.props.slug}?page=${page}`);
-
- if (!response.ok) {
- const errorData = await response.json();
- this.setState({ error: errorData.message });
- console.error('Failed to fetch data:', errorData.message);
- return;
- }
-
- const { messages, totalPages } = await response.json();
-
- this.setState({
- message: messages,
- totalPages: totalPages,
- error: null // clear any previous error
- });
-
- } catch (error) {
- this.setState({ error: `${error.message}` });
- console.error('Failed to fetch data:', error);
- }
- }
-
- refresh = async () => {
- await this.fetchMessages(this.state.page);
- }
-
- async componentDidMount() {
- await this.fetchMessages(this.state.page);
- }
-
- handleNext = async () => {
- const nextPage = this.state.page + 1;
- if (nextPage > this.state.totalPages) {
- return;
- }
- this.setState({ page: nextPage });
- await this.fetchMessages(nextPage);
- }
-
- handlePrevious = async () => {
- const previousPage = this.state.page - 1;
- this.setState({ page: previousPage });
- await this.fetchMessages(previousPage);
- }
-
- render() {
- const { message, error, page, totalPages } = this.state;
-
- return (
- <div>
- <BlogCommentsForm onMessageSent={this.refresh} slug={this.props.slug} />
- {error ? (
- <p>{error}</p>
- ) : message ? (
- <div>
- {message.map((g) => (
- <article className="card">
- <h1>{g.author}</h1>
- <div dangerouslySetInnerHTML={{__html: sanitizeHtml(g.comment)}}/>
- <small>{formatDate(g.created_at)}</small>
- </article>
- ))}
- {page > 1 && <button class="button margin" onClick={this.handlePrevious}>Previous</button>}
- {page < totalPages && <button class="button margin" onClick={this.handleNext}>Next</button>}
- </div>
- ) : (
- <p>Loading comments...</p>
- )}
- </div>
- );
- }
-}
-
-export default BlogComments;
+import { formatDate } from "../util";
+import sanitizeHtml from "sanitize-html";
+
+const BlogComments = ({ message, page, totalPages, handleNext, handlePrevious }) => (
+ <div>
+ {message.map((g) => (
+ <article className="card">
+ <h1>{g.author}</h1>
+ <div dangerouslySetInnerHTML={{ __html: sanitizeHtml(g.comment) }} />
+ <small>{formatDate(g.created_at)}</small>
+ </article>
+ ))}
+ {page > 1 && <button className="button margin" onClick={handlePrevious}>Previous</button>}
+ {page < totalPages && <button className="button margin" onClick={handleNext}>Next</button>}
+ </div>
+);
+
+export default withMessages(BlogComments, '/comments/:slug', BlogCommentsForm);
diff --git a/src/components/BlogCommentsForm.jsx b/src/components/BlogCommentsForm.jsx
index b48236e..d0c585c 100644
--- a/src/components/BlogCommentsForm.jsx
+++ b/src/components/BlogCommentsForm.jsx
@@ -1,124 +1,36 @@
-import { Component } from 'preact';
+import { h } from 'preact';
+import withFormHandling from './FormHandling';
import '../styles/Form.css';
-import { marked } from "marked";
-import DOMPurify from 'dompurify';
-class BlogCommentsForm extends Component {
- state = {
- author: '',
- comment: '',
- isMessageSent: false,
- commentId: ''
- };
-
- componentDidMount() {
- // Ensure the Turnstile script is loaded with explicit rendering
- const script = document.createElement('script');
- script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onloadTurnstileCallback';
- script.defer = true;
- document.body.appendChild(script);
-
- // Callback function to handle Turnstile token
- window.onloadTurnstileCallback = () => {
- window.turnstile.render('#turnstile-container', {
- sitekey: '0x4AAAAAAAdb4uvxFFzNEDxB',
- callback: (token) => {
- // Here you can handle the token, e.g., by storing it in a hidden form field
- this.setState({turnstileToken: token});
- },
- });
- };
-
- // Cleanup function to remove the script when the component unmounts
- return () => {
- document.body.removeChild(script);
- };
- }
-
- handleChange = (e) => {
- this.setState({ [e.target.name]: e.target.value });
- }
-
- handleSubmit = async (e) => {
- e.preventDefault();
-
- if (this.state.isMessageSent) {
- this.setState({
- errorMessage: 'You have already sent a comment.',
- });
- return;
- }
-
- try {
- const messageHtml = marked(DOMPurify.sanitize(this.state.comment));
- const { isMessageSent, errorMessage, ...messageData } = this.state; // Exclude isMessageSent from the data
-
- const response = await fetch(`${import.meta.env.PUBLIC_API_URL}/comments/${this.props.slug}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ ...messageData, comment: messageHtml }),
- });
-
- if (!response.ok) {
- const errorBody = await response.json();
- const errorMessage = `HTTP Error ${response.status}: ${response.statusText}<br>${errorBody.message}`;
-
- this.setState({
- errorMessage: `There was an error submitting your comment.<br>Details:<br>${errorMessage}`,
- isMessageSent: false,
- });
- return;
- }
-
- const responseBody = await response.json();
-
- this.setState({
- author: '',
- comment: '',
- isMessageSent: true,
- errorMessage: '',
- commentId: responseBody.id,
- });
-
- if (this.props.onMessageSent) {
- this.props.onMessageSent();
- }
- } catch (error) {
- this.setState({
- errorMessage: `There was an error submitting your message.<br>Details:<br>${error.message}<br>Check the console for more details.`,
- isMessageSent: false,
- });
+const BlogCommentsForm = ({ state, handleChange, handleSubmit }) => (
+ <div className="card">
+ <form onSubmit={handleSubmit}>
+ <h2>Submit Comment</h2>
+ <label htmlFor="author">Author *</label>
+ <input type="text" name="author" placeholder="John Doe" required value={state.author} onChange={handleChange} disabled={state.isMessageSent}/>
+ <label htmlFor="comment">Comment * (Supports <a href="https://www.markdownguide.org/cheat-sheet/" target="_blank">Markdown</a>)</label>
+ <textarea name="comment" placeholder="Enter your comment here." required value={state.comment}
+ onChange={handleChange} disabled={state.isMessageSent}></textarea>
+ <div id="turnstile-container"></div>
+ <button className="button" type="submit" disabled={state.isMessageSent}>Send</button>
+ </form>
+ {state.errorMessage && <p dangerouslySetInnerHTML={{__html: state.errorMessage}}/>}
+ {state.isMessageSent && !state.errorMessage &&
+ <div>
+ <p>Sent successfully!</p>
+ <p>(Optional)</p>
+ <p>Save this message ID: {state.commentId}</p>
+ <p>Then send it to me, to verify that you sent this comment.</p>
+ <p><a href="mailto:andrew@alee14.me">Email me</a> or tag/message me on these <a href="/contacts">platforms</a>.</p>
+ </div>
}
- }
+ </div>
+);
- render() {
- return (
- <div className="card">
- <form onSubmit={this.handleSubmit}>
- <h2>Submit Comment</h2>
- <label htmlFor="author">Author *</label>
- <input type="text" name="author" placeholder="John Doe" required value={this.state.author} onChange={this.handleChange} disabled={this.state.isMessageSent}/>
- <label htmlFor="comment">Comment * (Supports <a href="https://www.markdownguide.org/cheat-sheet/" target="_blank">Markdown</a>)</label>
- <textarea name="comment" placeholder="Enter your comment here." required value={this.state.comment}
- onChange={this.handleChange} disabled={this.state.isMessageSent}></textarea>
- <div id="turnstile-container"></div>
- <button class="button" type="submit" disabled={this.state.isMessageSent}>Send</button>
- </form>
- {this.state.errorMessage && <p dangerouslySetInnerHTML={{__html: this.state.errorMessage}}/>}
- {this.state.isMessageSent && !this.state.errorMessage &&
- <div>
- <p>Sent successfully!</p>
- <p>(Optional)</p>
- <p>Save this message ID: {this.state.commentId}</p>
- <p>Then send it to me, to verify that you sent this comment.</p>
- <p><a href="mailto:andrew@alee14.me">Email me</a> or tag/message me on these <a href="/contacts">platforms</a>.</p>
- </div>
- }
- </div>
- );
- }
-}
+const getBlogCommentsApiUrl = (props) => `${import.meta.env.PUBLIC_API_URL}/comments/${props.slug}`;
-export default BlogCommentsForm;
+export default withFormHandling(BlogCommentsForm, getBlogCommentsApiUrl, {
+ author: '',
+ comment: '',
+ commentId: ''
+});
diff --git a/src/components/FetchMessages.jsx b/src/components/FetchMessages.jsx
new file mode 100644
index 0000000..aa6f261
--- /dev/null
+++ b/src/components/FetchMessages.jsx
@@ -0,0 +1,86 @@
+import { Component } from 'preact';
+
+const withMessages = (WrappedComponent, apiEndpoint, FormComponent) => {
+ return class extends Component {
+ state = {
+ message: null,
+ error: null,
+ page: 1,
+ };
+
+ fetchMessages = async (page) => {
+ try {
+ const { slug } = this.props;
+ const response = await fetch(`${import.meta.env.PUBLIC_API_URL}${apiEndpoint.replace(':slug', slug)}?page=${page}`);
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ this.setState({ error: errorData.message });
+ console.error(errorData.message);
+ return;
+ }
+
+ const { messages, totalPages } = await response.json();
+
+ this.setState({
+ message: messages,
+ totalPages: totalPages,
+ error: null // clear any previous error
+ });
+
+ } catch (error) {
+ this.setState({ error: `Failed to fetch data: ${error.message}` });
+ console.error('Failed to fetch data:', error);
+ }
+ }
+
+ refresh = async () => {
+ await this.fetchMessages(this.state.page);
+ }
+
+ async componentDidMount() {
+ await this.fetchMessages(this.state.page);
+ }
+
+ handleNext = async () => {
+ const nextPage = this.state.page + 1;
+ if (nextPage > this.state.totalPages) {
+ return;
+ }
+ this.setState({ page: nextPage });
+ await this.fetchMessages(nextPage);
+ }
+
+ handlePrevious = async () => {
+ const previousPage = this.state.page - 1;
+ this.setState({ page: previousPage });
+ await this.fetchMessages(previousPage);
+ }
+
+ render() {
+ const { message, error, page, totalPages } = this.state;
+
+ return (
+ <div>
+ <FormComponent onMessageSent={this.refresh} {...this.props} />
+ {error ? (
+ <p>{error}</p>
+ ) : message ? (
+ <WrappedComponent
+ message={message}
+ page={page}
+ totalPages={totalPages}
+ handleNext={this.handleNext}
+ handlePrevious={this.handlePrevious}
+ {...this.props}
+ />
+ ) : (
+ <p>Loading messages...</p>
+ )}
+ </div>
+ );
+ }
+ };
+}
+
+export default withMessages;
diff --git a/src/components/FormHandling.jsx b/src/components/FormHandling.jsx
new file mode 100644
index 0000000..8f7f688
--- /dev/null
+++ b/src/components/FormHandling.jsx
@@ -0,0 +1,111 @@
+import { Component } from 'preact';
+import { marked } from 'marked';
+import DOMPurify from 'dompurify';
+
+const withFormHandling = (WrappedComponent, getApiUrl, initialState) => {
+ return class extends Component {
+ state = {
+ ...initialState,
+ isMessageSent: false,
+ errorMessage: '',
+ turnstileToken: ''
+ };
+
+ componentDidMount() {
+ // Ensure the Turnstile script is loaded with explicit rendering
+ const script = document.createElement('script');
+ script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onloadTurnstileCallback';
+ script.defer = true;
+ document.body.appendChild(script);
+
+ // Callback function to handle Turnstile token
+ window.onloadTurnstileCallback = () => {
+ window.turnstile.render('#turnstile-container', {
+ sitekey: '0x4AAAAAAAdb4uvxFFzNEDxB',
+ callback: (token) => {
+ // Here you can handle the token, e.g., by storing it in a hidden form field
+ this.setState({ turnstileToken: token });
+ },
+ });
+ };
+
+ // Cleanup function to remove the script when the component unmounts
+ return () => {
+ document.body.removeChild(script);
+ };
+ }
+
+ handleChange = (e) => {
+ this.setState({ [e.target.name]: e.target.value });
+ }
+
+ handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (this.state.isMessageSent) {
+ this.setState({
+ errorMessage: 'You have already sent a message.',
+ });
+ return;
+ }
+
+ try {
+ const messageKey = this.state.comment !== undefined ? 'comment' : 'message';
+ const messageHtml = marked(DOMPurify.sanitize(this.state[messageKey]));
+ const { isMessageSent, errorMessage, ...messageData } = this.state; // Exclude isMessageSent and errorMessage from the data
+
+ messageData[messageKey] = messageHtml;
+
+ const response = await fetch(getApiUrl(this.props), {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(messageData),
+ });
+
+ if (!response.ok) {
+ const errorBody = await response.json();
+ const errorMessage = `HTTP Error ${response.status}: ${response.statusText}<br>${errorBody.message}`;
+
+ this.setState({
+ errorMessage: `There was an error submitting your message.<br>Details:<br>${errorMessage}`,
+ isMessageSent: false,
+ });
+ return;
+ }
+
+ const responseBody = await response.json();
+
+ this.setState({
+ ...initialState,
+ isMessageSent: true,
+ errorMessage: '',
+ [messageKey === 'comment' ? 'commentId' : 'messageId']: responseBody.id,
+ });
+
+ if (this.props.onMessageSent) {
+ this.props.onMessageSent();
+ }
+ } catch (error) {
+ this.setState({
+ errorMessage: `There was an error submitting your message.<br>Details:<br>${error.message}<br>Check the console for more details.`,
+ isMessageSent: false,
+ });
+ }
+ }
+
+ render() {
+ return (
+ <WrappedComponent
+ {...this.props}
+ state={this.state}
+ handleChange={this.handleChange}
+ handleSubmit={this.handleSubmit}
+ />
+ );
+ }
+ };
+};
+
+export default withFormHandling;
diff --git a/src/components/Guestbook.jsx b/src/components/Guestbook.jsx
index e3238f9..a04990a 100644
--- a/src/components/Guestbook.jsx
+++ b/src/components/Guestbook.jsx
@@ -1,92 +1,23 @@
-import { Component } from 'preact';
-import { formatDate } from "../util";
-import sanitizeHtml from 'sanitize-html';
+import withMessages from './FetchMessages';
import GuestbookForm from "./GuestbookForm.jsx";
-
-class Guestbook extends Component {
- state = {
- message: null,
- error: null,
- page: 1,
- };
-
- fetchMessages = async (page) => {
- try {
- const response = await fetch(`${import.meta.env.PUBLIC_API_URL}/guestbook?page=${page}`);
-
- if (!response.ok) {
- const errorData = await response.json();
- this.setState({ error: `Failed to fetch data: ${errorData.message}` });
- console.error('Failed to fetch data:', errorData.message);
- return;
- }
-
- const { messages, totalPages } = await response.json();
-
- this.setState({
- message: messages,
- totalPages: totalPages,
- error: null // clear any previous error
- });
-
- } catch (error) {
- this.setState({ error: `Failed to fetch data: ${error.message}` });
- console.error('Failed to fetch data:', error);
- }
- }
-
- refresh = async () => {
- await this.fetchMessages(this.state.page);
- }
-
- async componentDidMount() {
- await this.fetchMessages(this.state.page);
- }
-
- handleNext = async () => {
- const nextPage = this.state.page + 1;
- if (nextPage > this.state.totalPages) {
- return;
- }
- this.setState({ page: nextPage });
- await this.fetchMessages(nextPage);
- }
-
- handlePrevious = async () => {
- const previousPage = this.state.page - 1;
- this.setState({ page: previousPage });
- await this.fetchMessages(previousPage);
- }
-
- render() {
- const { message, error, page, totalPages } = this.state;
-
- return (
- <div>
- <GuestbookForm onMessageSent={this.refresh} />
- {error ? (
- <p>{error}</p>
- ) : message ? (
- <div>
- <div className="grid">
- {message.map((g) => (
- <article className="card">
- <h1>Message from: {g.name}</h1>
- <small>{formatDate(g.created_at)}</small>
- <div dangerouslySetInnerHTML={{__html: sanitizeHtml(g.message)}}/>
- {g.website && <a href={g.website} target="_blank">My Website</a>}
- </article>
- ))}
- </div>
- {page > 1 && <button class="button margin" onClick={this.handlePrevious}>Previous</button>}
- {page < totalPages && <button class="button margin" onClick={this.handleNext}>Next</button>}
- </div>
- ) : (
- <p>Loading messages...</p>
- )}
- </div>
- );
- }
-}
-
-export default Guestbook;
+import { formatDate } from "../util";
+import sanitizeHtml from "sanitize-html";
+
+const Guestbook = ({ message, page, totalPages, handleNext, handlePrevious }) => (
+ <div>
+ <div className="grid">
+ {message.map((g) => (
+ <article className="card">
+ <h1>Message from: {g.name}</h1>
+ <small>{formatDate(g.created_at)}</small>
+ <div dangerouslySetInnerHTML={{ __html: sanitizeHtml(g.message) }} />
+ {g.website && <a href={g.website} target="_blank">My Website</a>}
+ </article>
+ ))}
+ </div>
+ {page > 1 && <button className="button margin" onClick={handlePrevious}>Previous</button>}
+ {page < totalPages && <button className="button margin" onClick={handleNext}>Next</button>}
+ </div>
+);
+
+export default withMessages(Guestbook, '/guestbook', GuestbookForm);
diff --git a/src/components/GuestbookForm.jsx b/src/components/GuestbookForm.jsx
index 1f1d3c9..4e06a27 100644
--- a/src/components/GuestbookForm.jsx
+++ b/src/components/GuestbookForm.jsx
@@ -1,128 +1,39 @@
-import { Component } from 'preact';
+import { h } from 'preact';
+import withFormHandling from './FormHandling';
import '../styles/Form.css';
-import { marked } from "marked";
-import DOMPurify from 'dompurify';
-class GuestbookForm extends Component {
- state = {
- name: '',
- website: '',
- message: '',
- isMessageSent: false,
- messageId: ''
- };
-
- componentDidMount() {
- // Ensure the Turnstile script is loaded with explicit rendering
- const script = document.createElement('script');
- script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onloadTurnstileCallback';
- script.defer = true;
- document.body.appendChild(script);
-
- // Callback function to handle Turnstile token
- window.onloadTurnstileCallback = () => {
- window.turnstile.render('#turnstile-container', {
- sitekey: '0x4AAAAAAAdb4uvxFFzNEDxB',
- callback: (token) => {
- // Here you can handle the token, e.g., by storing it in a hidden form field
- this.setState({turnstileToken: token});
- },
- });
- };
-
- // Cleanup function to remove the script when the component unmounts
- return () => {
- document.body.removeChild(script);
- };
- }
-
- handleChange = (e) => {
- this.setState({ [e.target.name]: e.target.value });
- }
-
- handleSubmit = async (e) => {
- e.preventDefault();
-
- if (this.state.isMessageSent) {
- this.setState({
- errorMessage: 'You have already sent a message.',
- });
- return;
- }
-
- try {
- const messageHtml = marked(DOMPurify.sanitize(this.state.message));
- const { isMessageSent, errorMessage, ...messageData } = this.state; // Exclude isMessageSent from the data
-
- const response = await fetch(`${import.meta.env.PUBLIC_API_URL}/guestbook`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ ...messageData, message: messageHtml }),
- });
-
- if (!response.ok) {
- const errorBody = await response.json();
- const errorMessage = `HTTP Error ${response.status}: ${response.statusText}<br>${errorBody.message}`;
-
- this.setState({
- errorMessage: `There was an error submitting your message.<br>Details:<br>${errorMessage}`,
- isMessageSent: false,
- });
- return;
- }
-
- const responseBody = await response.json();
-
- this.setState({
- name: '',
- website: '',
- message: '',
- isMessageSent: true,
- errorMessage: '',
- messageId: responseBody.id,
- });
-
- if (this.props.onMessageSent) {
- this.props.onMessageSent();
- }
- } catch (error) {
- this.setState({
- errorMessage: `There was an error submitting your message.<br>Details:<br>${error.message}<br>Check the console for more details.`,
- isMessageSent: false,
- });
+const GuestbookForm = ({ state, handleChange, handleSubmit }) => (
+ <div className="card">
+ <form onSubmit={handleSubmit}>
+ <h2>Submit Message</h2>
+ <label htmlFor="name">Name *</label>
+ <input type="text" name="name" placeholder="John Doe" required value={state.name} onChange={handleChange} disabled={state.isMessageSent}/>
+ <label htmlFor="website">Your Website (Optional)</label>
+ <input type="url" name="website" placeholder="https://example.com" value={state.website} onChange={handleChange} disabled={state.isMessageSent}/>
+ <label htmlFor="message">Message * (Supports <a href="https://www.markdownguide.org/cheat-sheet/" target="_blank">Markdown</a>)</label>
+ <textarea name="message" placeholder="Enter your message here." required value={state.message}
+ onChange={handleChange} disabled={state.isMessageSent}></textarea>
+ <div id="turnstile-container"></div>
+ <button className="button" type="submit" disabled={state.isMessageSent}>Send</button>
+ </form>
+ {state.errorMessage && <p dangerouslySetInnerHTML={{__html: state.errorMessage}}/>}
+ {state.isMessageSent && !state.errorMessage &&
+ <div>
+ <p>Sent successfully!</p>
+ <p>(Optional)</p>
+ <p>Save this message ID: {state.messageId}</p>
+ <p>Then send it to me, to verify that you sent this message.</p>
+ <p><a href="mailto:andrew@alee14.me">Email me</a> or tag/message me on these <a href="/contacts">platforms</a>.</p>
+ </div>
}
- }
+ </div>
+);
- render() {
- return (
- <div className="card">
- <form onSubmit={this.handleSubmit}>
- <h2>Submit Message</h2>
- <label htmlFor="name">Name *</label>
- <input type="text" name="name" placeholder="John Doe" required value={this.state.name} onChange={this.handleChange} disabled={this.state.isMessageSent}/>
- <label htmlFor="website">Your Website (Optional)</label>
- <input type="url" name="website" placeholder="https://example.com" value={this.state.website} onChange={this.handleChange} disabled={this.state.isMessageSent}/>
- <label htmlFor="message">Message * (Supports <a href="https://www.markdownguide.org/cheat-sheet/" target="_blank">Markdown</a>)</label>
- <textarea name="message" placeholder="Enter your message here." required value={this.state.message}
- onChange={this.handleChange} disabled={this.state.isMessageSent}></textarea>
- <div id="turnstile-container"></div>
- <button class="button" type="submit" disabled={this.state.isMessageSent}>Send</button>
- </form>
- {this.state.errorMessage && <p dangerouslySetInnerHTML={{__html: this.state.errorMessage}}/>}
- {this.state.isMessageSent && !this.state.errorMessage &&
- <div>
- <p>Sent successfully!</p>
- <p>(Optional)</p>
- <p>Save this message ID: {this.state.messageId}</p>
- <p>Then send it to me, to verify that you sent this message.</p>
- <p><a href="mailto:andrew@alee14.me">Email me</a> or tag/message me on these <a href="/contacts">platforms</a>.</p>
- </div>
- }
- </div>
- );
- }
-}
+const getGuestbookApiUrl = () => `${import.meta.env.PUBLIC_API_URL}/guestbook`;
-export default GuestbookForm;
+export default withFormHandling(GuestbookForm, getGuestbookApiUrl, {
+ name: '',
+ website: '',
+ message: '',
+ messageId: ''
+});