diff --git a/apps/the_monkeys/src/config/editor/editorjs.config.ts b/apps/the_monkeys/src/config/editor/editorjs.config.ts
index 6ea6a793..089f1a42 100644
--- a/apps/the_monkeys/src/config/editor/editorjs.config.ts
+++ b/apps/the_monkeys/src/config/editor/editorjs.config.ts
@@ -1,8 +1,12 @@
import CustomCodeTool from '@/components/editor/customBlocks/CodeBlock';
import CustomList from '@/components/editor/customBlocks/CustomListBlock';
import CustomEmbed from '@/components/editor/customBlocks/EmbedBlock';
-import { API_URL } from '@/constants/api';
-import axiosInstance from '@/services/api/axiosInstance';
+import PdfTool from '@/components/editor/customBlocks/PdfBlock';
+import VideoTool from '@/components/editor/customBlocks/VideoBlock';
+import { API_URL, API_URL_V2 } from '@/constants/api';
+import { storageV2 } from '@/services/storage/storageV2';
+// @ts-ignore
+import AttachesTool from '@editorjs/attaches';
import Delimiter from '@editorjs/delimiter';
import { EditorConfig } from '@editorjs/editorjs';
import Header from '@editorjs/header';
@@ -57,20 +61,29 @@ export const getEditorConfig = (blogId: string): EditorConfig => ({
captionPlaceholder: '',
uploader: {
async uploadByFile(file: File) {
- const formData = new FormData();
- formData.append('file', file);
+ try {
+ const response = await storageV2.uploadBlogImage(blogId, file);
+ const urlData = await storageV2.getBlogImageUrl(
+ blogId,
+ response.fileName
+ );
- const response = await axiosInstance.post(
- `/files/post/${blogId}`,
- formData
- );
-
- return {
- success: 1,
- file: {
- url: `${API_URL}/files/post/${blogId}/${response.data.new_file_name}`,
- },
- };
+ return {
+ success: 1,
+ file: {
+ url: urlData.url,
+ name: response.fileName, // Store for potential deletion
+ },
+ };
+ } catch (error) {
+ console.error('Image upload failed:', error);
+ return {
+ success: 0,
+ file: {
+ url: '',
+ },
+ };
+ }
},
},
},
@@ -83,6 +96,100 @@ export const getEditorConfig = (blogId: string): EditorConfig => ({
cols: 2,
},
},
+ attaches: {
+ class: AttachesTool,
+ config: {
+ uploader: {
+ async uploadByFile(file: File) {
+ try {
+ const response = await storageV2.uploadBlogFile(blogId, file);
+ const urlData = await storageV2.getBlogFileUrl(
+ blogId,
+ response.fileName
+ );
+
+ return {
+ success: 1,
+ file: {
+ url: urlData.url,
+ name: response.fileName,
+ size: response.size,
+ title: file.name,
+ extension: file.name.split('.').pop(),
+ },
+ };
+ } catch (error) {
+ console.error('File upload failed:', error);
+ return {
+ success: 0,
+ };
+ }
+ },
+ },
+ },
+ },
+ video: {
+ class: VideoTool,
+ config: {
+ onRemove: async (fileName: string) => {
+ try {
+ await storageV2.deleteBlogFile(blogId, fileName);
+ } catch (error) {
+ console.error('Failed to delete video from storage:', error);
+ }
+ },
+ uploader: async (file: File) => {
+ try {
+ const response = await storageV2.uploadBlogFile(blogId, file);
+ const urlData = await storageV2.getBlogFileUrl(
+ blogId,
+ response.fileName
+ );
+ return {
+ success: 1,
+ file: {
+ url: urlData.url,
+ name: response.fileName,
+ },
+ };
+ } catch (error) {
+ console.error('Video upload failed:', error);
+ return { success: 0 };
+ }
+ },
+ },
+ },
+ pdf: {
+ class: PdfTool,
+ config: {
+ onRemove: async (fileName: string) => {
+ try {
+ await storageV2.deleteBlogFile(blogId, fileName);
+ } catch (error) {
+ console.error('Failed to delete PDF from storage:', error);
+ }
+ },
+ uploader: async (file: File) => {
+ try {
+ const response = await storageV2.uploadBlogFile(blogId, file);
+ const urlData = await storageV2.getBlogFileUrl(
+ blogId,
+ response.fileName
+ );
+ return {
+ success: 1,
+ file: {
+ url: urlData.url,
+ name: response.fileName,
+ },
+ };
+ } catch (error) {
+ console.error('PDF upload failed:', error);
+ return { success: 0 };
+ }
+ },
+ },
+ },
},
defaultBlock: 'paragraph',
});
diff --git a/apps/the_monkeys/src/config/editor/editorjs_readonly.config.ts b/apps/the_monkeys/src/config/editor/editorjs_readonly.config.ts
index d2d5cfdb..3d7a31f1 100644
--- a/apps/the_monkeys/src/config/editor/editorjs_readonly.config.ts
+++ b/apps/the_monkeys/src/config/editor/editorjs_readonly.config.ts
@@ -1,7 +1,11 @@
import CustomCodeTool from '@/components/editor/customBlocks/CodeBlock';
import CustomList from '@/components/editor/customBlocks/CustomListBlock';
import CustomEmbed from '@/components/editor/customBlocks/EmbedBlock';
+import PdfTool from '@/components/editor/customBlocks/PdfBlock';
import TitleBlockTool from '@/components/editor/customBlocks/TitleBlock';
+import VideoTool from '@/components/editor/customBlocks/VideoBlock';
+// @ts-ignore
+import AttachesTool from '@editorjs/attaches';
import Delimiter from '@editorjs/delimiter';
import { EditorConfig } from '@editorjs/editorjs';
import Header from '@editorjs/header';
@@ -56,5 +60,47 @@ export const editorConfig: EditorConfig = {
captionPlaceholder: '',
},
},
+ attaches: {
+ class: class extends AttachesTool {
+ render() {
+ const wrapper = super.render();
+ const data = (this as any).data;
+
+ if (
+ data &&
+ data.file &&
+ data.file.url &&
+ data.file.url.toLowerCase().endsWith('.pdf')
+ ) {
+ // 1. Make the entire card clickable
+ wrapper.style.cursor = 'pointer';
+ wrapper.onclick = (e: MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const readerUrl = `/read/pdf?url=${encodeURIComponent(data.file.url)}&title=${encodeURIComponent(data.file.title || data.file.name || 'PDF Document')}`;
+ // 2. Open in same tab
+ window.location.href = readerUrl;
+ };
+
+ // 3. Optional: Fix dark mode visibility by ensuring the icon/text color is consistent
+ // The user mentioned the down arrow is not visible in dark mode.
+ // Since we are making it all clickable, we can just let the default UI be,
+ // but let's make sure the background on hover feels interactive.
+ wrapper.classList.add(
+ 'hover:bg-gray-50',
+ 'dark:hover:bg-zinc-900',
+ 'transition-colors'
+ );
+ }
+ return wrapper;
+ }
+ },
+ },
+ video: {
+ class: VideoTool,
+ },
+ pdf: {
+ class: PdfTool,
+ },
},
};
diff --git a/apps/the_monkeys/src/hooks/profile/useProfileImage.ts b/apps/the_monkeys/src/hooks/profile/useProfileImage.ts
index baff8655..c7154810 100644
--- a/apps/the_monkeys/src/hooks/profile/useProfileImage.ts
+++ b/apps/the_monkeys/src/hooks/profile/useProfileImage.ts
@@ -2,32 +2,36 @@
import { useEffect, useState } from 'react';
-import fetcher from '@/services/fileFetcher';
+import { storageV2 } from '@/services/storage/storageV2';
import { useQuery } from '@tanstack/react-query';
export const PROFILE_IMAGE_QUERY_KEY = 'profile-image';
const useProfileImage = (username: string | undefined) => {
- const [imageUrl, setImageUrl] = useState('');
-
- const { data, error, isLoading, isError } = useQuery({
+ const { data, error, isLoading, isError } = useQuery({
queryKey: [PROFILE_IMAGE_QUERY_KEY, username],
- queryFn: () => fetcher(`/files/profile/${username}/profile`),
+ queryFn: async () => {
+ if (!username) return null;
+ try {
+ const res = await storageV2.getProfileImageMeta(username);
+ return {
+ url: res.etag ? `${res.url}?v=${res.etag}` : res.url,
+ blurhash: res.blurhash,
+ };
+ } catch (error: any) {
+ if (error?.response?.status === 404) {
+ return null;
+ }
+ throw error;
+ }
+ },
enabled: !!username,
- staleTime: 5 * 60 * 1000,
+ staleTime: 15 * 60 * 1000, // 15 minutes
});
- useEffect(() => {
- if (!data) return;
-
- const objectUrl = URL.createObjectURL(data);
- setImageUrl(objectUrl);
-
- return () => URL.revokeObjectURL(objectUrl); // Cleanup on unmount
- }, [data]);
-
return {
- imageUrl,
+ imageUrl: data?.url || '',
+ blurHash: data?.blurhash || '',
isLoading,
isError: isError || !!error,
};
diff --git a/apps/the_monkeys/src/markdown/cookies.mdx b/apps/the_monkeys/src/markdown/cookies.mdx
index fb4232af..6411993e 100644
--- a/apps/the_monkeys/src/markdown/cookies.mdx
+++ b/apps/the_monkeys/src/markdown/cookies.mdx
@@ -1,45 +1,37 @@
-Welcome to Monkeys! We are dedicated to fostering a secure and transparent environment for our users.
-
-Our Cookie Policy explains how Monkeys ("we", "us", or "our") uses cookies and similar tracking technologies when you visit our website.
+Cookies might sound like something you'd find in a bakery, but on the internet, they're just small files that help us remember who you are and how you like to use our site.
## 1. What are cookies?
-Cookies are small text files that are stored on your device when you visit a website. They are widely used to make websites work more efficiently, as well as to provide information to the owners of the site.
+When you visit Monkeys, we put a tiny text file on your device. This helps the site load faster next time and remembers things like whether you prefer dark mode or light mode.
-## 2. How do we use cookies?
-
-**Essential Cookies**: These cookies are necessary for the website to function properly. They enable basic functions like page navigation and access to secure areas of the website. Our website cannot function properly without these cookies.
+## 2. How we use them
-**Analytics Cookies**: These cookies allow us to analyze how visitors use our website, so we can measure and improve the performance of our site.
+We use a few different types of cookies:
-**Advertising Cookies**: These cookies are used to personalize the advertising content that you see on our website. We may use third-party advertising companies to serve ads when you visit our website. These companies may use cookies to collect information about your visits to this and other websites in order to provide relevant advertisements about goods and services that may interest you.
+- **Essential Cookies**: These are the "must-haves". Without them, you wouldn't be able to log in or use the basic features of the site.
+- **Analytics Cookies**: These help us see which parts of the site are popular and which parts are confusing, so we can keep improving.
+- **Preferences**: To remember your settings so you don't have to keep choosing them every time you visit.
-## 3. Your Choices Regarding Cookies
+## 3. Your control
-You can control and/or delete cookies as you wish. You can delete all cookies that are already on your computer, and you can set most browsers to prevent them from being placed. If you do this, however, you may have to manually adjust some preferences every time you visit a site, and some services and functionalities may not work.
+You're the boss of your browser. Most browsers let you block or delete cookies in the settings. Just keep in mind that if you turn them off completely, Monkeys might not work as smoothly as you'd like.
-## 4. Changes to This Cookie Policy
-
-In our commitment to transparency, any revisions to our Privacy Policy will be promptly communicated.
+## 4. Updates
-We pledge to update this document as necessary, ensuring that users remain informed about our data practices and privacy protocols.
-
-Any alterations will be duly reflected on this page, providing clarity and accountability to our valued community.
+Digital tech moves fast, so we might update this policy occasionally. We'll always keep this page updated so you know exactly what's happening.
-## 5. Contact Us
-
-We welcome your questions and feedback.
+## 5. Get in touch
-If you have any questions about these policies, please contact us.
+If you have any questions about our use of cookies or anything else, we're here to help.
-Contact: **mail.themonkeys.life@gmail.com**
+Contact us at: **monkeys.admin@monkeys.com.co**
diff --git a/apps/the_monkeys/src/markdown/privacy.mdx b/apps/the_monkeys/src/markdown/privacy.mdx
index 880d15da..88a9d4b7 100644
--- a/apps/the_monkeys/src/markdown/privacy.mdx
+++ b/apps/the_monkeys/src/markdown/privacy.mdx
@@ -1,67 +1,54 @@
-Welcome to Monkeys! We are dedicated to fostering a secure and transparent environment for our users.
-
-This Privacy Policy serves to elucidate the nature of information we collect, how it is utilized, and the comprehensive measures we undertake to safeguard your privacy.
-
-Our Terms and Conditions ("Terms") govern all use of our Service and together with the Privacy Policy constitutes your agreement with us ("Agreement").
+At Monkeys, we believe in being open and honest about how we handle your data. We're committed to keeping your information safe and respecting your privacy.
-## 1. Information Collection and Use
-
-In our pursuit of providing optimal services, we gather various types of data to enhance user experience.
+## 1. What We Collect
-This encompasses information furnished directly by you, such as personal identifiers (e.g., name, email) when registering an account or engaging in interactions like leaving comments.
+To make Monkeys work for you, we collect a few pieces of information:
-Additionally, we collate data derived from your utilization of our services, including device details and browsing preferences, to refine our offerings.
+- **Things you tell us**: Like your name and email when you sign up.
+- **How you use the site**: We look at things like which articles you read and how you navigate the site so we can make it better.
+- **Tech details**: Basic stuff like your IP address (to help with security) and what kind of device you're using.
-## 2. How We Use Your Information
+## 2. How We Use It
-The information we gather serves multifaceted purposes aimed at bolstering our service provisions.
+We use your info to:
-Beyond facilitating the core functionalities of our platform, we leverage this data to continually refine and innovate our offerings. Moreover, we employ it to tailor your experience, ensuring personalized content delivery such as customized search results and relevant articles.
+- **Run the site**: So you can log in, post articles, and comment.
+- **Personalize your experience**: To show you content we think you'll actually like.
+- **Make things better**: Your feedback and usage patterns help us build new features.
+- **Security**: To keep out the bad actors and protect our community.
-## 3. Information Sharing
+## 3. Sharing your Data
-At Monkeys, preserving your privacy is paramount.
+We don't share your data with anyone, period. We definitely don't sell it for profit. The only way your data moves is if:
-Consequently, we adhere to stringent protocols regarding the sharing of personal information. Such data is never disclosed to external entities except under explicit user consent or in compliance with legal obligations.
+- **You Choose to Integrate**: If you connect third-party apps, you are in total control. You decide which apps can access your data, and we ensure they only see what's necessary, never your entire profile.
+- **Legal Requirements**: We only disclose information if we are legally compelled by a formal court order.
-## 4. Security
+## 4. Private Infrastructure
-Safeguarding the integrity and confidentiality of user data is central to our operational ethos. We maintain robust security frameworks to thwart unauthorized access, alteration, or dissemination of information.
+Unlike most platforms, we don't rely on the public cloud.
-Our unwavering commitment extends to fortifying both Monkeys infrastructure and the privacy rights of our esteemed users.
+- **Monkeys-Hosted Datacenter**: All your data is stored and processed in our own private, self-hosted datacenter.
+- **Our Hardware, Your Privacy**: By owning and managing our own hardware (instead of renting space from a cloud provider), we ensure your data stays secret and is never subjected to third-party oversight.
-## 5. Retention of Data
+## 5. Your Choices
-We will retain your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.
-
-We will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period, except when this data is used to strengthen the security or to improve the functionality of our Service, or we are legally obligated to retain this data for longer time periods.
+You're in control. You can update your profile info anytime. If you want to delete your data or your account, just let us know.
-## 6. Changes to This Privacy Policy
-
-In our commitment to transparency, any revisions to our Privacy Policy will be promptly communicated.
-
-We pledge to update this document as necessary, ensuring that users remain informed about our data practices and privacy protocols.
-
-Any alterations will be duly reflected on this page, providing clarity and accountability to our valued community.
-
-
-
-## 7. Contact Us
-
-We welcome your questions and feedback.
+## 6. Questions?
-If you have any questions about these policies, please contact us.
+If you're ever worried about your privacy or just want to chat about how we handle data, reach out!
-Contact: **mail.themonkeys.life@gmail.com**
+Contact us at: **monkeys.admin@monkeys.com.co**
diff --git a/apps/the_monkeys/src/markdown/terms.mdx b/apps/the_monkeys/src/markdown/terms.mdx
index 3ffe6c26..f864cd5d 100644
--- a/apps/the_monkeys/src/markdown/terms.mdx
+++ b/apps/the_monkeys/src/markdown/terms.mdx
@@ -1,79 +1,44 @@
-Welcome to Monkeys (“Company”, “we”, “our”, “us”)! As you have just clicked our Terms of Use, please pause, grab a cup of coffee and carefully read the following points. It will take you approximately 15 minutes.
-
-Our Privacy Policy also governs your use of our Service and explains how we collect, safeguard and disclose information that results from your use of our web pages.
-
-Your agreement with us includes these Terms and our Privacy Policy (“Agreements”). You acknowledge that you have read and understood Agreements, and agree to be bound of them.
+Welcome to Monkeys! We're glad you're here. This page is basically our "house rules" to make sure everyone has a great experience. By using Monkeys, you're agreeing to these rules, so please take a quick look.
-## 1. Acceptance of Terms
+## 1. Our Agreement
-By using our website, you agree to the Terms of Use. If you do not agree to these terms, you should not use this site. Your continued use of the site after any modifications to these terms constitutes acceptance of those changes.
+When you use our website, you're agreeing to these terms. If you don't agree with them, that's okay, but you shouldn't use the site. We might update these rules from time to time, and continuing to use Monkeys means you're okay with the changes.
-## 2. License to Use Site
-
-Monkeys grants you a limited, revocable, non-exclusive license to access and make personal use of this site.
+## 2. Your Content
-This license does not include any resale or commercial use of the site or its contents; any derivative use of this site or its contents; any downloading or copying of account information for the benefit of another merchant; or any use of data mining, robots, or similar data gathering and extraction tools.
-
-You may not frame or utilize framing techniques to enclose any trademark, logo, or other proprietary information (including images, text, page layout, or form) of Monkeys without express written consent. Any unauthorized use terminates the permission or license granted by Monkeys.
+You own what you post on Monkeys. However, by sharing it here, you're giving us permission to show it to the world. You're also responsible for making sure you actually have the rights to what you're posting and that it doesn't hurt anyone or break any laws.
-## 3. User Responsibilities
+## 3. Community Rules
-Users are solely responsible for any content they post on Monkeys. By posting content, you warrant and represent that you own or otherwise control all of the rights to your content and that the content is accurate, does not violate these Terms of Use, and will not cause injury to any person or entity.
+We want Monkeys to be a positive place. This means:
-We reserve the right to remove any content that we believe violates these Terms of Use or is otherwise objectionable, without prior notice and at our sole discretion.
+- **No illegal stuff**: Don't use our site for anything against the law.
+- **Respect others**: Don't be a bully, and don't share things that are harmful or fraudulent.
+- **Protect children**: Don't share anything that exploits or harms minors.
+- **Don't break the tech**: Don't try to hack us or use automated tools to scrape our data without permission.
-## 4. Intellectual Property
-
-All content on this site, including text, graphics, logos, images, audio clips, video clips, digital downloads, data compilations, and software, is the property of Monkeys or its content suppliers and is protected by international copyright laws.
+## 4. Our Property
-The compilation of all content on this site is the exclusive property of Monkeys and is protected by international copyright laws. All software used on this site is the property of Monkeys or its software suppliers and is protected by international copyright laws.
+The Monkeys logo, our code, and the overall design belong to us. The articles and content belong to the people who wrote them. Please don't copy our stuff or use it for commercial purposes without asking first.
-## 5. Prohibited Uses
-
-You may use Service only for lawful purposes and in accordance with Terms. You agree not to use Service:
+## 5. Ending our Relationship
-In any way that violates any applicable national or international law or regulation.
-
-For the purpose of exploiting, harming, or attempting to exploit or harm minors in any way by exposing them to inappropriate content or otherwise.
-
-In any way that infringes upon the rights of others, or in any way is illegal, threatening, fraudulent, or harmful, or in connection with any unlawful, illegal, fraudulent, or harmful purpose or activity.
-
-To engage in any other conduct that restricts or inhibits anyone’s use or enjoyment of Service, or which, as determined by us, may harm or offend Company or users of Service or expose them to liability.
+We hope you stay forever, but we can't always guarantee that. If someone breaks these rules, we might have to suspend or close their account. If you want to leave, you can stop using the service at any time.
-## 6. Termination
-
-We may terminate or suspend your account and bar access to Service immediately, without prior notice or liability, under our sole discretion, for any reason whatsoever and without limitation, including but not limited to a breach of Terms.
-
-If you wish to terminate your account, you may simply discontinue using Service.
-
-All provisions of Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability.
-
-
-
-## 7. Changes to Terms of Use
-
-We reserve the right to update or modify these Terms of Use at any time without prior notice. It is your responsibility to review these Terms of Use periodically for changes.
-
-Your continued use of the site following the posting of any changes to these Terms of Use constitutes acceptance of those changes.
-
-
-
-## 8. Contact Us
-
-We welcome your questions and feedback.
+## 6. Keeping it Simple
-If you have any questions about these policies, please contact us.
+We tried to keep this easy to read. If you have any questions or if something isn't clear, we'd love to hear from you.
-Contact: **mail.themonkeys.life@gmail.com**
+Contact us at: **monkeys.admin@monkeys.com.co**
diff --git a/apps/the_monkeys/src/services/api/axiosInstanceV2.ts b/apps/the_monkeys/src/services/api/axiosInstanceV2.ts
index 26d9253c..a180d6ae 100644
--- a/apps/the_monkeys/src/services/api/axiosInstanceV2.ts
+++ b/apps/the_monkeys/src/services/api/axiosInstanceV2.ts
@@ -5,7 +5,7 @@ import { setupRefreshInterceptor } from './interceptors';
const axiosInstanceV2 = axios.create({
baseURL: '/api/v2',
- timeout: 30000,
+ timeout: 3600000,
});
axiosInstanceV2.interceptors.request.use(
diff --git a/apps/the_monkeys/src/services/storage/storageV2.ts b/apps/the_monkeys/src/services/storage/storageV2.ts
new file mode 100644
index 00000000..273b4aeb
--- /dev/null
+++ b/apps/the_monkeys/src/services/storage/storageV2.ts
@@ -0,0 +1,115 @@
+import axiosInstanceV2 from '../api/axiosInstanceV2';
+
+export interface StorageV2FileMeta {
+ object: string;
+ etag: string;
+ size: number;
+ contentType: string;
+ lastModified: string;
+ cacheControl: string;
+ blurhash?: string;
+ width?: number;
+ height?: number;
+ url: string;
+}
+
+export interface StorageV2UploadResponse {
+ bucket: string;
+ object: string;
+ fileName: string;
+ etag: string;
+ size: number;
+ contentType: string;
+}
+
+export interface StorageV2UrlResponse {
+ url: string;
+ expiresIn: number;
+}
+
+export const storageV2 = {
+ // Profile Image
+ uploadProfileImage: async (userId: string, file: File) => {
+ const formData = new FormData();
+ formData.append('profile_pic', file);
+
+ const response = await axiosInstanceV2.post(
+ `/storage/profiles/${userId}/profile`,
+ formData,
+ {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ }
+ );
+ return response.data;
+ },
+
+ getProfileImageMeta: async (userId: string) => {
+ const response = await axiosInstanceV2.get(
+ `/storage/profiles/${userId}/profile/meta`
+ );
+ return response.data;
+ },
+
+ getProfileImageUrl: async (userId: string) => {
+ const response = await axiosInstanceV2.get(
+ `/storage/profiles/${userId}/profile/url`
+ );
+ return response.data;
+ },
+
+ deleteProfileImage: async (userId: string) => {
+ const response = await axiosInstanceV2.delete(
+ `/storage/profiles/${userId}/profile`
+ );
+ return response.data;
+ },
+
+ // Blog Images
+ uploadBlogImage: async (blogId: string, file: File) => {
+ const formData = new FormData();
+ formData.append('file', file);
+
+ const response = await axiosInstanceV2.post(
+ `/storage/posts/${blogId}`,
+ formData,
+ {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ }
+ );
+ return response.data;
+ },
+
+ getBlogImageMeta: async (blogId: string, fileName: string) => {
+ const response = await axiosInstanceV2.get(
+ `/storage/posts/${blogId}/${fileName}/meta`
+ );
+ return response.data;
+ },
+
+ getBlogImageUrl: async (blogId: string, fileName: string) => {
+ const response = await axiosInstanceV2.get(
+ `/storage/posts/${blogId}/${fileName}/url`
+ );
+ return response.data;
+ },
+
+ // Generic File Support (Aliases for clarity)
+ uploadBlogFile: async (blogId: string, file: File) => {
+ return storageV2.uploadBlogImage(blogId, file);
+ },
+
+ getBlogFileUrl: async (blogId: string, fileName: string) => {
+ return storageV2.getBlogImageUrl(blogId, fileName);
+ },
+
+ deleteBlogFile: async (blogId: string, fileName: string) => {
+ const response = await axiosInstanceV2.delete(
+ `/storage/posts/${blogId}/${fileName}`
+ );
+ return response.data;
+ },
+};
diff --git a/apps/the_monkeys/src/utils/blurhash.ts b/apps/the_monkeys/src/utils/blurhash.ts
new file mode 100644
index 00000000..d76e99c3
--- /dev/null
+++ b/apps/the_monkeys/src/utils/blurhash.ts
@@ -0,0 +1,31 @@
+import { decode } from 'blurhash';
+
+/**
+ * Decodes a BlurHash string to a base64 DataURL.
+ * This can be used as a placeholder for Next.js Image component.
+ */
+export const decodeBlurHashToDataURL = (
+ blurhash: string | undefined,
+ width: number = 32,
+ height: number = 32
+): string | undefined => {
+ if (!blurhash) return undefined;
+
+ try {
+ const pixels = decode(blurhash, width, height);
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return undefined;
+
+ const imageData = ctx.createImageData(width, height);
+ imageData.data.set(pixels);
+ ctx.putImageData(imageData, 0, 0);
+
+ return canvas.toDataURL();
+ } catch (error) {
+ console.error('Failed to decode blurhash:', error);
+ return undefined;
+ }
+};
diff --git a/apps/the_monkeys/src/utils/clientInfo.ts b/apps/the_monkeys/src/utils/clientInfo.ts
index 8e11be7f..63df8696 100644
--- a/apps/the_monkeys/src/utils/clientInfo.ts
+++ b/apps/the_monkeys/src/utils/clientInfo.ts
@@ -1,5 +1,4 @@
import Bowser from 'bowser';
-import { publicIpv4 } from 'public-ip';
export interface ClientInfoData {
ip: string;
@@ -45,7 +44,11 @@ class ClientInfo {
try {
// Get public IP address with timeout
this.ip = await Promise.race([
- publicIpv4(),
+ (async () => {
+ const res = await fetch('https://api.ipify.org?format=json');
+ const data = await res.json();
+ return data.ip || 'unknown';
+ })(),
new Promise((resolve) =>
setTimeout(() => resolve('unknown'), 5000)
),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0218e6e9..b85d3b7c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -30,6 +30,9 @@ importers:
apps/the_monkeys:
dependencies:
+ '@editorjs/attaches':
+ specifier: ^1.3.1
+ version: 1.3.2
'@editorjs/delimiter':
specifier: 1.4.2
version: 1.4.2
@@ -81,6 +84,9 @@ importers:
axios:
specifier: ^1.6.8
version: 1.8.4
+ blurhash:
+ specifier: ^2.0.5
+ version: 2.0.5
bowser:
specifier: ^2.11.0
version: 2.11.0
@@ -120,9 +126,6 @@ importers:
prismjs:
specifier: ^1.30.0
version: 1.30.0
- public-ip:
- specifier: ^7.0.1
- version: 7.0.1
react:
specifier: ^18
version: 18.3.1
@@ -515,6 +518,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@codexteam/ajax@4.2.0':
+ resolution: {integrity: sha512-54r/HZirqBPEV8rM9gZh570RCwG6M/iDAXT9Q9eGuMo9KZU49tw1dEDHjYsResGckfCsaymDqg4GnhfrBtX9JQ==}
+
'@codexteam/icons@0.0.4':
resolution: {integrity: sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q==}
@@ -555,6 +561,10 @@ packages:
resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
engines: {node: '>=18'}
+ '@editorjs/attaches@1.3.2':
+ resolution: {integrity: sha512-hzwcOo/Lk1hTEzXXV0MSnwPsGIILaeM1R7SCMu4uUALftlQVugf+Vm4a9Rd66IT7TU3tfyiOwJPb/Ydp9zn36A==}
+ engines: {node: '>=20.0.0'}
+
'@editorjs/delimiter@1.4.2':
resolution: {integrity: sha512-S8q2LpeYdYkVShLp7K8c4HLthDHBevLw+sT+iO0+SH0oMvFmld9SUon3DFzMQ2gG07EOdZGRZ958+sVxyvFjZw==}
@@ -997,9 +1007,6 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
- '@leichtgewicht/ip-codec@2.0.5':
- resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
-
'@mdx-js/loader@3.1.0':
resolution: {integrity: sha512-xU/lwKdOyfXtQGqn3VnJjlDrmKXEvMi1mgYxVmukEUtVycIz1nh7oQ40bKTd4cA7rLStqu0740pnhGYxGoqsCg==}
peerDependencies:
@@ -1656,10 +1663,6 @@ packages:
'@rushstack/eslint-patch@1.11.0':
resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==}
- '@sindresorhus/is@5.6.0':
- resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
- engines: {node: '>=14.16'}
-
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
@@ -1669,10 +1672,6 @@ packages:
'@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
- '@szmarczak/http-timer@5.0.1':
- resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
- engines: {node: '>=14.16'}
-
'@tanstack/query-core@5.69.0':
resolution: {integrity: sha512-Kn410jq6vs1P8Nm+ZsRj9H+U3C0kjuEkYLxbiCyn3MDEiYor1j2DGVULqAz62SLZtUZ/e9Xt6xMXiJ3NJ65WyQ==}
@@ -1862,9 +1861,6 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
- '@types/http-cache-semantics@4.0.4':
- resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
-
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
@@ -2218,6 +2214,9 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
+ blurhash@2.0.5:
+ resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==}
+
bowser@2.11.0:
resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
@@ -2244,14 +2243,6 @@ packages:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
- cacheable-lookup@7.0.0:
- resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
- engines: {node: '>=14.16'}
-
- cacheable-request@10.2.14:
- resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==}
- engines: {node: '>=14.16'}
-
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
@@ -2315,10 +2306,6 @@ packages:
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
- clone-regexp@3.0.0:
- resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==}
- engines: {node: '>=12'}
-
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@@ -2357,10 +2344,6 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
- convert-hrtime@5.0.0:
- resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==}
- engines: {node: '>=12'}
-
convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
@@ -2582,10 +2565,6 @@ packages:
decode-named-character-reference@1.1.0:
resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==}
- decompress-response@6.0.0:
- resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
- engines: {node: '>=10'}
-
deep-eql@5.0.2:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
@@ -2593,10 +2572,6 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
- defer-to-connect@2.0.1:
- resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
- engines: {node: '>=10'}
-
define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
@@ -2636,14 +2611,6 @@ packages:
dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
- dns-packet@5.6.1:
- resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==}
- engines: {node: '>=6'}
-
- dns-socket@4.2.2:
- resolution: {integrity: sha512-BDeBd8najI4/lS00HSKpdFia+OvUMytaVjfzR9n5Lq8MlZRSvtbI+uLtx1+XmQFls5wFU9dssccTmQQ6nfpjdg==}
- engines: {node: '>=6'}
-
doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
@@ -2996,10 +2963,6 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
- form-data-encoder@2.1.4:
- resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==}
- engines: {node: '>= 14.17'}
-
form-data@4.0.2:
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
engines: {node: '>= 6'}
@@ -3018,10 +2981,6 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
- function-timeout@0.1.1:
- resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==}
- engines: {node: '>=14.16'}
-
function.prototype.name@1.1.8:
resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
engines: {node: '>= 0.4'}
@@ -3045,10 +3004,6 @@ packages:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
- get-stream@6.0.1:
- resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
- engines: {node: '>=10'}
-
get-symbol-description@1.1.0:
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
engines: {node: '>= 0.4'}
@@ -3100,10 +3055,6 @@ packages:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
- got@13.0.0:
- resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==}
- engines: {node: '>=16'}
-
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -3156,9 +3107,6 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
- http-cache-semantics@4.1.1:
- resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
-
http-parser-js@0.5.9:
resolution: {integrity: sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==}
@@ -3166,10 +3114,6 @@ packages:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
- http2-wrapper@2.2.1:
- resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==}
- engines: {node: '>=10.19.0'}
-
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
@@ -3213,10 +3157,6 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
- ip-regex@5.0.0:
- resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
is-alphabetical@2.0.1:
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
@@ -3291,10 +3231,6 @@ packages:
is-hexadecimal@2.0.1:
resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
- is-ip@5.0.1:
- resolution: {integrity: sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==}
- engines: {node: '>=14.16'}
-
is-map@2.0.3:
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
engines: {node: '>= 0.4'}
@@ -3322,10 +3258,6 @@ packages:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
- is-regexp@3.1.0:
- resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==}
- engines: {node: '>=12'}
-
is-set@2.0.3:
resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
engines: {node: '>= 0.4'}
@@ -3504,10 +3436,6 @@ packages:
loupe@3.1.3:
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
- lowercase-keys@3.0.0:
- resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -3666,14 +3594,6 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
- mimic-response@3.1.0:
- resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
- engines: {node: '>=10'}
-
- mimic-response@4.0.0:
- resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -3722,6 +3642,7 @@ packages:
next@14.2.26:
resolution: {integrity: sha512-b81XSLihMwCfwiUVRRja3LphLo4uBBMZEzBBWMaISbKTwOmq3wPknIETy/8000tr7Gq4WmbuFYPS7jOYIf+ZJw==}
engines: {node: '>=18.17.0'}
+ deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
@@ -3748,10 +3669,6 @@ packages:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
- normalize-url@8.0.1:
- resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==}
- engines: {node: '>=14.16'}
-
nwsapi@2.2.19:
resolution: {integrity: sha512-94bcyI3RsqiZufXjkr3ltkI86iEl+I7uiHVDtcq9wJUTwYQJ5odHDeSzkkrRzi80jJ8MaeZgqKjH1bAWAFw9bA==}
@@ -3802,10 +3719,6 @@ packages:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
- p-cancelable@3.0.0:
- resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
- engines: {node: '>=12.20'}
-
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -4016,10 +3929,6 @@ packages:
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
- public-ip@7.0.1:
- resolution: {integrity: sha512-DdNcqcIbI0wEeCBcqX+bmZpUCvrDMJHXE553zgyG1MZ8S1a/iCCxmK9iTjjql+SpHSv4cZkmRv5/zGYW93AlCw==}
- engines: {node: '>=18'}
-
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -4030,10 +3939,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- quick-lru@5.1.1:
- resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
- engines: {node: '>=10'}
-
react-day-picker@8.10.1:
resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
peerDependencies:
@@ -4174,9 +4079,6 @@ packages:
requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
- resolve-alpn@1.2.1:
- resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
-
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -4193,10 +4095,6 @@ packages:
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
hasBin: true
- responselike@3.0.0:
- resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
- engines: {node: '>=14.16'}
-
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -4430,10 +4328,6 @@ packages:
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
- super-regex@0.2.0:
- resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==}
- engines: {node: '>=14.16'}
-
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -4476,10 +4370,6 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
- time-span@5.1.0:
- resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
- engines: {node: '>=12'}
-
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -4799,6 +4689,7 @@ packages:
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
@@ -5133,6 +5024,8 @@ snapshots:
'@biomejs/cli-win32-x64@1.9.4':
optional: true
+ '@codexteam/ajax@4.2.0': {}
+
'@codexteam/icons@0.0.4': {}
'@codexteam/icons@0.0.5': {}
@@ -5161,6 +5054,11 @@ snapshots:
'@csstools/css-tokenizer@3.0.3': {}
+ '@editorjs/attaches@1.3.2':
+ dependencies:
+ '@codexteam/ajax': 4.2.0
+ '@codexteam/icons': 0.3.3
+
'@editorjs/delimiter@1.4.2':
dependencies:
'@codexteam/icons': 0.3.3
@@ -5537,8 +5435,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
- '@leichtgewicht/ip-codec@2.0.5': {}
-
'@mdx-js/loader@3.1.0(acorn@8.14.1)':
dependencies:
'@mdx-js/mdx': 3.1.0(acorn@8.14.1)
@@ -6168,8 +6064,6 @@ snapshots:
'@rushstack/eslint-patch@1.11.0': {}
- '@sindresorhus/is@5.6.0': {}
-
'@socket.io/component-emitter@3.1.2': {}
'@swc/counter@0.1.3': {}
@@ -6179,10 +6073,6 @@ snapshots:
'@swc/counter': 0.1.3
tslib: 2.8.1
- '@szmarczak/http-timer@5.0.1':
- dependencies:
- defer-to-connect: 2.0.1
-
'@tanstack/query-core@5.69.0': {}
'@tanstack/query-devtools@5.67.2': {}
@@ -6406,8 +6296,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
- '@types/http-cache-semantics@4.0.4': {}
-
'@types/json5@0.0.29': {}
'@types/mdast@4.0.4':
@@ -6771,6 +6659,8 @@ snapshots:
binary-extensions@2.3.0: {}
+ blurhash@2.0.5: {}
+
bowser@2.11.0: {}
brace-expansion@1.1.11:
@@ -6799,18 +6689,6 @@ snapshots:
cac@6.7.14: {}
- cacheable-lookup@7.0.0: {}
-
- cacheable-request@10.2.14:
- dependencies:
- '@types/http-cache-semantics': 4.0.4
- get-stream: 6.0.1
- http-cache-semantics: 4.1.1
- keyv: 4.5.4
- mimic-response: 4.0.0
- normalize-url: 8.0.1
- responselike: 3.0.0
-
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@@ -6879,10 +6757,6 @@ snapshots:
client-only@0.0.1: {}
- clone-regexp@3.0.0:
- dependencies:
- is-regexp: 3.1.0
-
clsx@2.1.1: {}
cmdk@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
@@ -6917,8 +6791,6 @@ snapshots:
concat-map@0.0.1: {}
- convert-hrtime@5.0.0: {}
-
convert-source-map@1.9.0: {}
convert-source-map@2.0.0: {}
@@ -7153,16 +7025,10 @@ snapshots:
dependencies:
character-entities: 2.0.2
- decompress-response@6.0.0:
- dependencies:
- mimic-response: 3.1.0
-
deep-eql@5.0.2: {}
deep-is@0.1.4: {}
- defer-to-connect@2.0.1: {}
-
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.1
@@ -7199,14 +7065,6 @@ snapshots:
dlv@1.1.3: {}
- dns-packet@5.6.1:
- dependencies:
- '@leichtgewicht/ip-codec': 2.0.5
-
- dns-socket@4.2.2:
- dependencies:
- dns-packet: 5.6.1
-
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
@@ -7731,8 +7589,6 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
- form-data-encoder@2.1.4: {}
-
form-data@4.0.2:
dependencies:
asynckit: 0.4.0
@@ -7749,8 +7605,6 @@ snapshots:
function-bind@1.1.2: {}
- function-timeout@0.1.1: {}
-
function.prototype.name@1.1.8:
dependencies:
call-bind: 1.0.8
@@ -7784,8 +7638,6 @@ snapshots:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
- get-stream@6.0.1: {}
-
get-symbol-description@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -7854,20 +7706,6 @@ snapshots:
gopd@1.2.0: {}
- got@13.0.0:
- dependencies:
- '@sindresorhus/is': 5.6.0
- '@szmarczak/http-timer': 5.0.1
- cacheable-lookup: 7.0.0
- cacheable-request: 10.2.14
- decompress-response: 6.0.0
- form-data-encoder: 2.1.4
- get-stream: 6.0.1
- http2-wrapper: 2.2.1
- lowercase-keys: 3.0.0
- p-cancelable: 3.0.0
- responselike: 3.0.0
-
graceful-fs@4.2.11: {}
graphemer@1.4.0: {}
@@ -7949,8 +7787,6 @@ snapshots:
html-escaper@2.0.2: {}
- http-cache-semantics@4.1.1: {}
-
http-parser-js@0.5.9: {}
http-proxy-agent@7.0.2:
@@ -7960,11 +7796,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- http2-wrapper@2.2.1:
- dependencies:
- quick-lru: 5.1.1
- resolve-alpn: 1.2.1
-
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.3
@@ -8004,8 +7835,6 @@ snapshots:
internmap@2.0.3: {}
- ip-regex@5.0.0: {}
-
is-alphabetical@2.0.1: {}
is-alphanumerical@2.0.1:
@@ -8086,11 +7915,6 @@ snapshots:
is-hexadecimal@2.0.1: {}
- is-ip@5.0.1:
- dependencies:
- ip-regex: 5.0.0
- super-regex: 0.2.0
-
is-map@2.0.3: {}
is-number-object@1.1.1:
@@ -8113,8 +7937,6 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.2
- is-regexp@3.1.0: {}
-
is-set@2.0.3: {}
is-shared-array-buffer@1.0.4:
@@ -8319,8 +8141,6 @@ snapshots:
loupe@3.1.3: {}
- lowercase-keys@3.0.0: {}
-
lru-cache@10.4.3: {}
lru-cache@5.1.1:
@@ -8667,10 +8487,6 @@ snapshots:
dependencies:
mime-db: 1.52.0
- mimic-response@3.1.0: {}
-
- mimic-response@4.0.0: {}
-
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -8740,8 +8556,6 @@ snapshots:
normalize-range@0.1.2: {}
- normalize-url@8.0.1: {}
-
nwsapi@2.2.19: {}
object-assign@4.1.1: {}
@@ -8807,8 +8621,6 @@ snapshots:
object-keys: 1.1.1
safe-push-apply: 1.0.0
- p-cancelable@3.0.0: {}
-
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@@ -8952,20 +8764,12 @@ snapshots:
proxy-from-env@1.1.0: {}
- public-ip@7.0.1:
- dependencies:
- dns-socket: 4.2.2
- got: 13.0.0
- is-ip: 5.0.1
-
punycode@2.3.1: {}
querystringify@2.2.0: {}
queue-microtask@1.2.3: {}
- quick-lru@5.1.1: {}
-
react-day-picker@8.10.1(date-fns@3.6.0)(react@18.3.1):
dependencies:
date-fns: 3.6.0
@@ -9162,8 +8966,6 @@ snapshots:
requires-port@1.0.0: {}
- resolve-alpn@1.2.1: {}
-
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -9180,10 +8982,6 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
- responselike@3.0.0:
- dependencies:
- lowercase-keys: 3.0.0
-
reusify@1.1.0: {}
rimraf@3.0.2:
@@ -9506,12 +9304,6 @@ snapshots:
pirates: 4.0.6
ts-interface-checker: 0.1.13
- super-regex@0.2.0:
- dependencies:
- clone-regexp: 3.0.0
- function-timeout: 0.1.1
- time-span: 5.1.0
-
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
@@ -9574,10 +9366,6 @@ snapshots:
dependencies:
any-promise: 1.3.0
- time-span@5.1.0:
- dependencies:
- convert-hrtime: 5.0.0
-
tinybench@2.9.0: {}
tinyexec@0.3.2: {}