8 Commits

Author SHA1 Message Date
5362ff7599 feat: switch back to meta llama
Some checks failed
Deploy / lint-build-deploy (push) Failing after 1m7s
2026-02-15 10:30:08 +00:00
21b3c3ba83 feat: switch to deepseek llama
Some checks failed
Deploy / lint-build-deploy (push) Failing after 1m52s
2026-02-08 10:22:08 +00:00
44b9793c60 feat: switch to umami
Some checks failed
Deploy / lint-build-deploy (push) Failing after 1m51s
2026-01-31 22:50:00 +01:00
35020f2499 feat: switch to sweego
Some checks failed
Deploy / lint-build-deploy (push) Failing after 1m19s
2026-01-30 18:12:29 +01:00
20b09849bc revert: restore lucide-react icons for emails
Some checks failed
Deploy / lint-build-deploy (push) Failing after 1m33s
2026-01-29 19:42:21 +01:00
dc3850ac4d fix: rename env var
Some checks failed
Deploy / lint-build-deploy (push) Failing after 1m23s
2026-01-29 18:19:00 +01:00
e73262b3b3 chore: update privacy
Some checks failed
Deploy / lint-build-deploy (push) Failing after 2m7s
2026-01-28 19:56:07 +01:00
7299a266f1 fix: correct infrastructure path
All checks were successful
Deploy / lint-build-deploy (push) Successful in 2m10s
2026-01-22 17:44:36 +00:00
29 changed files with 221 additions and 1167 deletions

View File

@@ -1,6 +1,6 @@
ADMIN_EMAIL="" ADMIN_EMAIL=""
CRON_SECRET="" CRON_SECRET=""
HOME_URL="" NEXT_PUBLIC_HOME_URL=""
OVHCLOUD_API_KEY="" OVHCLOUD_API_KEY=""
MAINTENANCE_MODE="0" MAINTENANCE_MODE="0"
NEWS_LIMIT="50" NEWS_LIMIT="50"
@@ -8,7 +8,8 @@ NEWS_TO_USE="10"
NEXT_PUBLIC_BRAND_COUNTRY="" NEXT_PUBLIC_BRAND_COUNTRY=""
NEXT_PUBLIC_BRAND_EMAIL="" NEXT_PUBLIC_BRAND_EMAIL=""
NEXT_PUBLIC_BRAND_NAME="" NEXT_PUBLIC_BRAND_NAME=""
NEXT_PUBLIC_BRAND_OWNER_NAME=""
DATABASE_URL="" DATABASE_URL=""
RESEND_FROM="" SWEEGO_API_KEY=""
RESEND_KEY="" SWEEGO_FROM=""
SECRET_HASH="" SECRET_HASH=""

View File

@@ -36,6 +36,6 @@ jobs:
ssh debian@51.210.247.57 << 'EOF' ssh debian@51.210.247.57 << 'EOF'
cd /home/debian/newsletter-hackernews cd /home/debian/newsletter-hackernews
git pull origin main git pull origin main
cd /home/debian/gitea cd /home/debian/infrastructure
docker compose up -d --build newsletter docker compose up -d --build newsletter
EOF EOF

View File

@@ -11,10 +11,14 @@ COPY . .
ARG NEXT_PUBLIC_BRAND_NAME ARG NEXT_PUBLIC_BRAND_NAME
ARG NEXT_PUBLIC_BRAND_EMAIL ARG NEXT_PUBLIC_BRAND_EMAIL
ARG NEXT_PUBLIC_BRAND_COUNTRY ARG NEXT_PUBLIC_BRAND_COUNTRY
ARG NEXT_PUBLIC_HOME_URL
ARG NEXT_PUBLIC_BRAND_OWNER_NAME
ENV NEXT_PUBLIC_BRAND_NAME=$NEXT_PUBLIC_BRAND_NAME ENV NEXT_PUBLIC_BRAND_NAME=$NEXT_PUBLIC_BRAND_NAME
ENV NEXT_PUBLIC_BRAND_EMAIL=$NEXT_PUBLIC_BRAND_EMAIL ENV NEXT_PUBLIC_BRAND_EMAIL=$NEXT_PUBLIC_BRAND_EMAIL
ENV NEXT_PUBLIC_BRAND_COUNTRY=$NEXT_PUBLIC_BRAND_COUNTRY ENV NEXT_PUBLIC_BRAND_COUNTRY=$NEXT_PUBLIC_BRAND_COUNTRY
ENV NEXT_PUBLIC_HOME_URL=$NEXT_PUBLIC_HOME_URL
ENV NEXT_PUBLIC_BRAND_OWNER_NAME=$NEXT_PUBLIC_BRAND_OWNER_NAME
RUN npm run build RUN npm run build

View File

@@ -14,8 +14,8 @@ export const dynamic = 'force-dynamic'; // defaults to force-static
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
if (!process.env.RESEND_KEY) { if (!process.env.SWEEGO_API_KEY) {
throw new Error('Resend variables not set'); throw new Error('SWEEGO_API_KEY is not set');
} }
const body = await request.json(); const body = await request.json();
const validation = ConfirmationSchema.safeParse(body); const validation = ConfirmationSchema.safeParse(body);

View File

@@ -1,7 +1,7 @@
import { NewsletterTemplate } from '@components/email/Newsletter'; import { NewsletterTemplate } from '@components/email/Newsletter';
import prisma from '@prisma/prisma'; import prisma from '@prisma/prisma';
import { formatApiResponse } from '@utils/formatApiResponse'; import { formatApiResponse } from '@utils/formatApiResponse';
import { sender } from '@utils/resendClient'; import { sender } from '@utils/sweego';
import { import {
INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR,
STATUS_INTERNAL_SERVER_ERROR, STATUS_INTERNAL_SERVER_ERROR,

View File

@@ -1,7 +1,7 @@
import { ConfirmationTemplate } from '@components/email/Confirmation'; import { ConfirmationTemplate } from '@components/email/Confirmation';
import prisma from '@prisma/prisma'; import prisma from '@prisma/prisma';
import { formatApiResponse } from '@utils/formatApiResponse'; import { formatApiResponse } from '@utils/formatApiResponse';
import { sender } from '@utils/resendClient'; import { sender } from '@utils/sweego';
import { import {
BAD_REQUEST, BAD_REQUEST,
INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR,

View File

@@ -1,7 +1,7 @@
import { UnsubscribeTemplate } from '@components/email/Unsubscribe'; import { UnsubscribeTemplate } from '@components/email/Unsubscribe';
import prisma from '@prisma/prisma'; import prisma from '@prisma/prisma';
import { formatApiResponse } from '@utils/formatApiResponse'; import { formatApiResponse } from '@utils/formatApiResponse';
import { sender } from '@utils/resendClient'; import { sender } from '@utils/sweego';
import { import {
BAD_REQUEST, BAD_REQUEST,
INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR,
@@ -30,13 +30,10 @@ export async function POST(request: NextRequest) {
} }
}); });
if (user && !user.deleted) { if (user) {
await prisma.user.update({ await prisma.user.delete({
where: { where: {
email email
},
data: {
deleted: true
} }
}); });

View File

@@ -86,7 +86,7 @@ const Confirmation = () => {
'@type': 'WebSite', '@type': 'WebSite',
name: 'Hackernews Newsletter', name: 'Hackernews Newsletter',
title: 'Subscription Confirmation', title: 'Subscription Confirmation',
url: `${process.env.HOME_URL}/confirmation` url: `${process.env.NEXT_PUBLIC_HOME_URL}/confirmation`
}; };
return ( return (

View File

@@ -1,8 +1,8 @@
import { Tiles } from '@components/tiles/Tiles'; import { Tiles } from '@components/tiles/Tiles';
import { cn } from '@utils/cn'; import { cn } from '@utils/cn';
import { Analytics } from '@vercel/analytics/react';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { Inter as FontSans } from 'next/font/google'; import { Inter as FontSans } from 'next/font/google';
import Script from 'next/script';
import './globals.css'; import './globals.css';
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -34,8 +34,12 @@ export default function RootLayout({
<Tiles> <Tiles>
<div className='z-10'>{children}</div> <div className='z-10'>{children}</div>
</Tiles> </Tiles>
<Analytics />
</body> </body>
<Script
defer
src='https://analytics.frompixels.com/script.js'
data-website-id='588e7b7d-e9cd-4b96-94bf-8269c499b0a2'
/>
</html> </html>
); );
} }

View File

@@ -31,7 +31,7 @@ export const Home = () => {
'@type': 'WebSite', '@type': 'WebSite',
name: 'Hackernews Newsletter', name: 'Hackernews Newsletter',
title: 'Home', title: 'Home',
url: process.env.HOME_URL url: process.env.NEXT_PUBLIC_HOME_URL
}; };
const form = useForm<SubscribeFormType>({ const form = useForm<SubscribeFormType>({

View File

@@ -2,458 +2,113 @@
import { CustomCard } from '@components/CustomCard'; import { CustomCard } from '@components/CustomCard';
import { SchemaOrg } from '@components/SchemaOrg'; import { SchemaOrg } from '@components/SchemaOrg';
import { useState, useEffect } from 'react';
const useObfuscatedEmail = () => {
const [email, setEmail] = useState<string | null>(null);
useEffect(() => {
setEmail(process.env.NEXT_PUBLIC_BRAND_EMAIL || null);
}, []);
return email;
};
const Privacy = () => { const Privacy = () => {
const email = useObfuscatedEmail();
const schema = { const schema = {
'@context': 'https://schema.org', '@context': 'https://schema.org',
'@type': 'WebSite', '@type': 'WebSite',
name: 'Hackernews Newsletter', name: 'Hackernews Newsletter',
title: 'Privacy Policy', title: 'Privacy Policy',
url: `${process.env.HOME_URL}/privacy` url: `${process.env.NEXT_PUBLIC_HOME_URL}/privacy`
}; };
const body = ( const body = (
<div className='privacy-content my-2 max-h-[50vh] space-y-1 overflow-auto'> <div className='privacy-content my-2 max-h-[50vh] space-y-1 overflow-auto'>
<h2>Who We Are</h2>
<p className='leading-relaxed'> <p className='leading-relaxed'>
This Privacy Policy describes Our policies and procedures on the Data controller: {process.env.NEXT_PUBLIC_BRAND_OWNER_NAME}, an
collection, use and disclosure of Your information when You use the individual based in {process.env.NEXT_PUBLIC_BRAND_COUNTRY}.
Service and tells You about Your privacy rights and how the law protects
You.
</p> </p>
<p> <p>
We use Your Personal data to provide and improve the Service. By using Contact:{' '}
the Service, You agree to the collection and use of information in
accordance with this Privacy Policy.
</p>
<h2>Interpretation and Definitions</h2>
<h3>Interpretation</h3>
<p className='leading-relaxed'>
The words of which the initial letter is capitalized have meanings
defined under the following conditions. The following definitions shall
have the same meaning regardless of whether they appear in singular or
in plural.
</p>
<h3>Definitions</h3>
<p className='leading-relaxed'>
For the purposes of this Privacy Policy:
</p>
<ul className='list-disc space-y-4 pl-6'>
<li>
<p>
<strong>Account</strong> means a unique account created for You to
access our Service or parts of our Service.
</p>
</li>
<li>
<p>
<strong>Affiliate</strong> means an entity that controls, is
controlled by or is under common control with a party, where
&quot;control&quot; means ownership of 50% or more of the shares,
equity interest or other securities entitled to vote for election of
directors or other managing authority.
</p>
</li>
<li>
<p>
<strong>Application</strong> refers to{' '}
{process.env.NEXT_PUBLIC_BRAND_NAME}, the software program provided
by the Company.
</p>
</li>
<li>
<p>
<strong>Company</strong> (referred to as either &quot;the
Company&quot;, &quot;We&quot;, &quot;Us&quot; or &quot;Our&quot; in
this Agreement) refers to {process.env.NEXT_PUBLIC_BRAND_NAME}.
</p>
</li>
<li>
<p>
<strong>Country</strong> refers to:{' '}
{process.env.NEXT_PUBLIC_BRAND_COUNTRY}
</p>
</li>
<li>
<p>
<strong>Device</strong> means any device that can access the Service
such as a computer, a cellphone or a digital tablet.
</p>
</li>
<li>
<p>
<strong>Personal Data</strong> is any information that relates to an
identified or identifiable individual.
</p>
</li>
<li>
<p>
<strong>Service</strong> refers to the Application.
</p>
</li>
<li>
<p>
<strong>Service Provider</strong> means any natural or legal person
who processes the data on behalf of the Company. It refers to
third-party companies or individuals employed by the Company to
facilitate the Service, to provide the Service on behalf of the
Company, to perform services related to the Service or to assist the
Company in analyzing how the Service is used.
</p>
</li>
<li>
<p>
<strong>Usage Data</strong> refers to data collected automatically,
either generated by the use of the Service or from the Service
infrastructure itself (for example, the duration of a page visit).
</p>
</li>
<li>
<p>
<strong>You</strong> means the individual accessing or using the
Service, or the company, or other legal entity on behalf of which
such individual is accessing or using the Service, as applicable.
</p>
</li>
</ul>
<h2>Data Collection and Usage</h2>
<h3>Types of Data Collected</h3>
<h4>Personal Data</h4>
<p className='leading-relaxed'>
While using Our Service, We may ask You to provide Us with certain
personally identifiable information that can be used to contact or
identify You. Personally identifiable information may include, but is
not limited to:
</p>
<ul className='list-disc space-y-4 pl-6'>
<li>
<p>Email address</p>
</li>
<li>
<p>Usage Data</p>
</li>
</ul>
<h4>Usage Data</h4>
<p className='leading-relaxed'>
Usage Data is collected automatically when using the Service.
</p>
<p>
Usage Data may include information such as Your Device&apos;s Internet
Protocol address (e.g. IP address), browser type, browser version, the
pages of our Service that You visit, the time and date of Your visit,
the time spent on those pages, unique device identifiers and other
diagnostic data.
</p>
<p>
When You access the Service by or through a mobile device, We may
collect certain information automatically, including, but not limited
to, the type of mobile device You use, Your mobile device unique ID, the
IP address of Your mobile device, Your mobile operating system, the type
of mobile Internet browser You use, unique device identifiers and other
diagnostic data.
</p>
<p>
We may also collect information that Your browser sends whenever You
visit our Service or when You access the Service by or through a mobile
device.
</p>
<h2>Use of Your Personal Data</h2>
<p className='leading-relaxed'>
The Company may use Personal Data for the following purposes:
</p>
<ul className='list-disc space-y-4 pl-6'>
<li>
<p>
<strong>To provide and maintain our Service</strong>, including to
monitor the usage of our Service.
</p>
</li>
<li>
<p>
<strong>To manage Your Account:</strong> to manage Your registration
as a user of the Service. The Personal Data You provide can give You
access to different functionalities of the Service that are
available to You as a registered user.
</p>
</li>
<li>
<p>
<strong>For the performance of a contract:</strong> the development,
compliance and undertaking of the purchase contract for the
products, items or services You have purchased or of any other
contract with Us through the Service.
</p>
</li>
<li>
<p>
<strong>To contact You:</strong> To contact You by email, telephone
calls, SMS, or other equivalent forms of electronic communication,
such as a mobile application&apos;s push notifications regarding
updates or informative communications related to the
functionalities, products or contracted services, including the
security updates, when necessary or reasonable for their
implementation.
</p>
</li>
<li>
<p>
<strong>To provide You</strong> with news, special offers and
general information about other goods, services and events which we
offer that are similar to those that you have already purchased or
enquired about unless You have opted not to receive such
information.
</p>
</li>
<li>
<p>
<strong>To manage Your requests:</strong> To attend and manage Your
requests to Us.
</p>
</li>
<li>
<p>
<strong>For business transfers:</strong> We may use Your information
to evaluate or conduct a merger, divestiture, restructuring,
reorganization, dissolution, or other sale or transfer of some or
all of Our assets, whether as a going concern or as part of
bankruptcy, liquidation, or similar proceeding, in which Personal
Data held by Us about our Service users is among the assets
transferred.
</p>
</li>
<li>
<p>
<strong>For other purposes</strong>: We may use Your information for
other purposes, such as data analysis, identifying usage trends,
determining the effectiveness of our promotional campaigns and to
evaluate and improve our Service, products, services, marketing and
your experience.
</p>
</li>
</ul>
<p>We may share Your personal information in the following situations:</p>
<ul className='list-disc space-y-4 pl-6'>
<li>
<strong>With Service Providers:</strong> We may share Your personal
information with Service Providers to monitor and analyze the use of
our Service, to contact You.
</li>
<li>
<strong>For business transfers:</strong> We may share or transfer Your
personal information in connection with, or during negotiations of,
any merger, sale of Company assets, financing, or acquisition of all
or a portion of Our business to another company.
</li>
<li>
<strong>With Affiliates:</strong> We may share Your information with
Our affiliates, in which case we will require those affiliates to
honor this Privacy Policy. Affiliates include Our parent company and
any other subsidiaries, joint venture partners or other companies that
We control or that are under common control with Us.
</li>
<li>
<strong>With business partners:</strong> We may share Your information
with Our business partners to offer You certain products, services or
promotions.
</li>
<li>
<strong>With other users:</strong> when You share personal information
or otherwise interact in the public areas with other users, such
information may be viewed by all users and may be publicly distributed
outside.
</li>
<li>
<strong>With Your consent</strong>: We may disclose Your personal
information for any other purpose with Your consent.
</li>
</ul>
<h2>Data Handling and Security</h2>
<h3>Retention of Your Personal Data</h3>
<p className='leading-relaxed'>
The Company 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.
</p>
<p>
The Company will also retain Usage Data for internal analysis purposes.
Usage Data is generally retained for a shorter period of time, 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.
</p>
<h3>Transfer of Your Personal Data</h3>
<p className='leading-relaxed'>
Your information, including Personal Data, is processed at the
Company&apos;s operating offices and in any other places where the
parties involved in the processing are located. It means that this
information may be transferred to and maintained on computers
located outside of Your state, province, country or other governmental
jurisdiction where the data protection laws may differ than those from
Your jurisdiction.
</p>
<h3>Security of Your Personal Data</h3>
<p className='leading-relaxed'>
Your consent to this Privacy Policy followed by Your submission of such
information represents Your agreement to that transfer.
</p>
<p>
The Company will take all steps reasonably necessary to ensure that Your
data is treated securely and in accordance with this Privacy Policy and
no transfer of Your Personal Data will take place to an organization or
a country unless there are adequate controls in place including the
security of Your data and other personal information.
</p>
<h4 className='text-lg font-medium text-gray-800'>
Delete Your Personal Data
</h4>
<p className='leading-relaxed'>
You have the right to delete or request that We assist in deleting the
Personal Data that We have collected about You.
</p>
<p>
Our Service may give You the ability to delete certain information about
You from within the Service.
</p>
<p>
You may update, amend, or delete Your information at any time by signing
in to Your Account, if you have one, and visiting the account settings
section that allows you to manage Your personal information. You may
also contact Us to request access to, correct, or delete any personal
information that You have provided to Us.
</p>
<p>
Please note, however, that We may need to retain certain information
when we have a legal obligation or lawful basis to do so.
</p>
<h2>Legal Disclosures</h2>
<h3>Business Transactions</h3>
<p className='leading-relaxed'>
If the Company is involved in a merger, acquisition or asset sale, Your
Personal Data may be transferred. We will provide notice before Your
Personal Data is transferred and becomes subject to a different Privacy
Policy.
</p>
<h3>Law Enforcement</h3>
<p className='leading-relaxed'>
Under certain circumstances, the Company may be required to disclose
Your Personal Data if required to do so by law or in response to valid
requests by public authorities (e.g. a court or a government agency).
</p>
<h3>Other Legal Requirements</h3>
<p className='leading-relaxed'>
The Company may disclose Your Personal Data in the good faith belief
that such action is necessary to:
</p>
<ul className='list-disc space-y-4 pl-6'>
<li>Comply with a legal obligation</li>
<li>Protect and defend the rights or property of the Company</li>
<li>
Prevent or investigate possible wrongdoing in connection with the
Service
</li>
<li>
Protect the personal safety of Users of the Service or the public
</li>
<li>Protect against legal liability</li>
</ul>
<h2>Additional Information</h2>
<p className='leading-relaxed'>
The security of Your Personal Data is important to Us, but remember that
no method of transmission over the Internet, or method of electronic
storage is 100% secure. While We strive to use commercially acceptable
means to protect Your Personal Data, We cannot guarantee its absolute
security.
</p>
<h3>Children&apos;s Privacy</h3>
<p className='leading-relaxed'>
Our Service does not address anyone under the age of 13. We do not
knowingly collect personally identifiable information from anyone under
the age of 13. If You are a parent or guardian and You are aware that
Your child has provided Us with Personal Data, please contact Us. If We
become aware that We have collected Personal Data from anyone under the
age of 13 without verification of parental consent, We take steps to
remove that information from Our servers.
</p>
<p>
If We need to rely on consent as a legal basis for processing Your
information and Your country requires consent from a parent, We may
require Your parent&apos;s consent before We collect and use that
information.
</p>
<h3>Links to Other Websites</h3>
<p className='leading-relaxed'>
Our Service may contain links to other websites that are not operated by
Us. If You click on a third party link, You will be directed to that
third party&apos;s site. We strongly advise You to review the Privacy
Policy of every site You visit.
</p>
<p>
We have no control over and assume no responsibility for the content,
privacy policies or practices of any third party sites or services.
</p>
<h3>Changes to this Privacy Policy</h3>
<p className='leading-relaxed'>
We may update Our Privacy Policy from time to time. We will notify You
of any changes by posting the new Privacy Policy on this page.
</p>
<p>
We will let You know via email and/or a prominent notice on Our Service,
prior to the change becoming effective and update the &quot;Last
updated&quot; date at the top of this Privacy Policy.
</p>
<p>
You are advised to review this Privacy Policy periodically for any
changes. Changes to this Privacy Policy are effective when they are
posted on this page.
</p>
<h2>Contact Information</h2>
<p className='leading-relaxed'>
If you have any questions about this Privacy Policy, You can contact us
by writing to{' '}
{email ? (
<a <a
href={`mailto:${email}`} href={`mailto:${process.env.NEXT_PUBLIC_BRAND_EMAIL}`}
className='text-purple-600 hover:text-purple-700' className='text-purple-600 hover:text-purple-700'
> >
{email} {process.env.NEXT_PUBLIC_BRAND_EMAIL}
</a>
</p>
<h2>What We Collect</h2>
<p className='leading-relaxed'>
Your email address (required to send the newsletter).
</p>
<h2>Why We Collect It</h2>
<p className='leading-relaxed'>
To deliver the daily {process.env.NEXT_PUBLIC_BRAND_NAME}{' '}
newsletter&mdash;a digest of top Hacker News stories with AI-generated
commentary.
</p>
<p>
We do not sell products, track your activity beyond essential delivery,
or share your data for marketing.
</p>
<h2>Legal Basis</h2>
<p className='leading-relaxed'>
Your explicit consent via double opt-in signup (you receive a
confirmation email with an activation link).
</p>
<h2>Third Parties</h2>
<ul className='list-disc space-y-4 pl-6'>
<li>
<strong>Email delivery:</strong> Resend (US). GDPR-compliant email
infrastructure.
</li>
<li>
<strong>Hosting &amp; analytics:</strong> Vercel (US). Analytics are
anonymized and aggregated&mdash;no personal data beyond your email is
collected.
</li>
<li>
<strong>Content source:</strong> Public Hacker News API (Y Combinator,
US).
</li>
</ul>
<h2>Your Rights</h2>
<ul className='list-disc space-y-4 pl-6'>
<li>Unsubscribe anytime via the link in every email</li>
<li>
Request deletion of your email by contacting{' '}
<a
href={`mailto:${process.env.NEXT_PUBLIC_BRAND_EMAIL}`}
className='text-purple-600 hover:text-purple-700'
>
{process.env.NEXT_PUBLIC_BRAND_EMAIL}
</a>
</li>
<li>
Upon unsubscribe or deletion request, your email is permanently
deleted from our database
</li>
</ul>
<h2>Data Retention</h2>
<p className='leading-relaxed'>
We retain your email address only until you unsubscribe.
</p>
<h2>International Transfers</h2>
<p className='leading-relaxed'>
We rely on EU-approved safeguards including Standard Contractual Clauses
to ensure adequate protection when using US-based services.
</p>
<h2>Changes</h2>
<p className='leading-relaxed'>
We may update this policy occasionally. The latest version will always
be available at{' '}
<a
href={`${process.env.NEXT_PUBLIC_HOME_URL}/privacy`}
target='_blank'
rel='noopener noreferrer'
className='text-purple-600 hover:text-purple-700'
>
{process.env.NEXT_PUBLIC_HOME_URL}/privacy
</a> </a>
) : (
<span className='text-gray-400'>loading...</span>
)}
. .
</p> </p>
</div> </div>
@@ -465,7 +120,7 @@ const Privacy = () => {
<CustomCard <CustomCard
className='max-90vh max-90vw' className='max-90vh max-90vw'
title='Privacy Policy' title='Privacy Policy'
description='Last updated: November 23, 2024' description='Last updated: January 28, 2026'
content={body} content={body}
/> />
</> </>

View File

@@ -6,6 +6,6 @@ export default function robots(): MetadataRoute.Robots {
userAgent: '*', userAgent: '*',
disallow: '' disallow: ''
}, },
sitemap: `${process.env.HOME_URL!}/sitemap.xml` sitemap: `${process.env.NEXT_PUBLIC_HOME_URL!}/sitemap.xml`
}; };
} }

View File

@@ -3,19 +3,19 @@ import { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap { export default function sitemap(): MetadataRoute.Sitemap {
return [ return [
{ {
url: process.env.HOME_URL!, url: process.env.NEXT_PUBLIC_HOME_URL!,
lastModified: new Date(), lastModified: new Date(),
changeFrequency: 'yearly', changeFrequency: 'yearly',
priority: 1 priority: 1
}, },
{ {
url: `${process.env.HOME_URL!}/privacy`, url: `${process.env.NEXT_PUBLIC_HOME_URL!}/privacy`,
lastModified: new Date(), lastModified: new Date(),
changeFrequency: 'yearly', changeFrequency: 'yearly',
priority: 0.5 priority: 0.5
}, },
{ {
url: `${process.env.HOME_URL!}/unsubscribe`, url: `${process.env.NEXT_PUBLIC_HOME_URL!}/unsubscribe`,
lastModified: new Date(), lastModified: new Date(),
changeFrequency: 'yearly', changeFrequency: 'yearly',
priority: 0.2 priority: 0.2

View File

@@ -33,7 +33,7 @@ const Unsubscribe = () => {
'@type': 'WebSite', '@type': 'WebSite',
name: 'Hackernews Newsletter', name: 'Hackernews Newsletter',
title: 'Unsubscribe', title: 'Unsubscribe',
url: `${process.env.HOME_URL}/unsubscribe` url: `${process.env.NEXT_PUBLIC_HOME_URL}/unsubscribe`
}; };
const form = useForm<UnsubscribeFormType>({ const form = useForm<UnsubscribeFormType>({

View File

@@ -22,7 +22,7 @@ export const ConfirmationTemplate = (code: string) => {
}} }}
> >
<a <a
href={`${process.env.HOME_URL}/confirmation?code=${code}`} href={`${process.env.NEXT_PUBLIC_HOME_URL}/confirmation?code=${code}`}
style={{ style={{
display: 'inline-block', display: 'inline-block',
padding: '12px 24px', padding: '12px 24px',

View File

@@ -22,7 +22,7 @@ export const UnsubscribeTemplate = () => {
}} }}
> >
<a <a
href={`${process.env.HOME_URL}/`} href={`${process.env.NEXT_PUBLIC_HOME_URL}/`}
style={{ style={{
display: 'inline-block', display: 'inline-block',
padding: '12px 24px', padding: '12px 24px',

View File

@@ -1,17 +1,12 @@
const iconStyle = { import {
display: 'inline-block', User,
verticalAlign: 'middle' Building2,
}; Mail,
LogOut,
const Icon = ({ name, size = 16 }: { name: string; size?: number }) => ( LayoutGrid,
<img Shield,
src={`${process.env.HOME_URL}/email-icons/${name}.png`} Home
width={size} } from 'lucide-react';
height={size}
alt=""
style={iconStyle}
/>
);
export const Footer = () => { export const Footer = () => {
return ( return (
@@ -49,7 +44,7 @@ export const Footer = () => {
letterSpacing: '0.05em' letterSpacing: '0.05em'
}} }}
> >
<Icon name="user" size={16} /> <User size={16} color='#386FA4' />
Contact Us Contact Us
</h4> </h4>
<p <p
@@ -62,7 +57,7 @@ export const Footer = () => {
color: '#4A5568' color: '#4A5568'
}} }}
> >
<Icon name="building-2" size={14} /> <Building2 size={14} color='#386FA4' />
{process.env.NEXT_PUBLIC_BRAND_NAME} {process.env.NEXT_PUBLIC_BRAND_NAME}
</p> </p>
<p <p
@@ -75,7 +70,7 @@ export const Footer = () => {
color: '#4A5568' color: '#4A5568'
}} }}
> >
<Icon name="mail" size={14} /> <Mail size={14} color='#386FA4' />
<a <a
href={`mailto:${process.env.NEXT_PUBLIC_BRAND_EMAIL}`} href={`mailto:${process.env.NEXT_PUBLIC_BRAND_EMAIL}`}
style={{ color: '#386FA4', textDecoration: 'none' }} style={{ color: '#386FA4', textDecoration: 'none' }}
@@ -93,11 +88,11 @@ export const Footer = () => {
color: '#4A5568' color: '#4A5568'
}} }}
> >
<Icon name="log-out" size={14} /> <LogOut size={14} color='#386FA4' />
<span> <span>
Click{' '} Click{' '}
<a <a
href={`${process.env.HOME_URL}/unsubscribe`} href={`${process.env.NEXT_PUBLIC_HOME_URL}/unsubscribe`}
style={{ color: '#386FA4', textDecoration: 'none' }} style={{ color: '#386FA4', textDecoration: 'none' }}
> >
here here
@@ -129,7 +124,7 @@ export const Footer = () => {
letterSpacing: '0.05em' letterSpacing: '0.05em'
}} }}
> >
<Icon name="layout-grid" size={16} /> <LayoutGrid size={16} color='#386FA4' />
Quick Links Quick Links
</h4> </h4>
<p <p
@@ -142,9 +137,9 @@ export const Footer = () => {
color: '#4A5568' color: '#4A5568'
}} }}
> >
<Icon name="shield" size={14} /> <Shield size={14} color='#386FA4' />
<a <a
href={`${process.env.HOME_URL}/privacy`} href={`${process.env.NEXT_PUBLIC_HOME_URL}/privacy`}
style={{ color: '#386FA4', textDecoration: 'none' }} style={{ color: '#386FA4', textDecoration: 'none' }}
> >
Privacy Policy Privacy Policy
@@ -160,9 +155,9 @@ export const Footer = () => {
color: '#4A5568' color: '#4A5568'
}} }}
> >
<Icon name="house" size={14} /> <Home size={14} color='#386FA4' />
<a <a
href={process.env.HOME_URL} href={process.env.NEXT_PUBLIC_HOME_URL}
style={{ color: '#386FA4', textDecoration: 'none' }} style={{ color: '#386FA4', textDecoration: 'none' }}
> >
Visit Website Visit Website

421
package-lock.json generated
View File

@@ -13,7 +13,6 @@
"@prisma/client": "^5.6.0", "@prisma/client": "^5.6.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@vercel/analytics": "^1.1.1",
"axios": "^1.12.0", "axios": "^1.12.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
@@ -25,7 +24,6 @@
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.48.2", "react-hook-form": "^7.48.2",
"resend": "^3.1.0",
"tailwind-merge": "^2.1.0", "tailwind-merge": "^2.1.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8" "zod": "^3.23.8"
@@ -1546,12 +1544,6 @@
"node": ">=12.4.0" "node": ">=12.4.0"
} }
}, },
"node_modules/@one-ini/wasm": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"license": "MIT"
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1715,24 +1707,6 @@
} }
} }
}, },
"node_modules/@react-email/render": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-0.0.16.tgz",
"integrity": "sha512-wDaMy27xAq1cJHtSFptp0DTKPuV2GYhloqia95ub/DH9Dea1aWYsbdM918MOc/b/HvVS3w1z8DWzfAk13bGStQ==",
"license": "MIT",
"dependencies": {
"html-to-text": "9.0.5",
"js-beautify": "^1.14.11",
"react-promise-suspense": "0.3.4"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
},
"node_modules/@rtsao/scc": { "node_modules/@rtsao/scc": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1747,19 +1721,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@selderee/plugin-htmlparser2": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
"integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"selderee": "^0.11.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.15", "version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -2343,53 +2304,6 @@
"win32" "win32"
] ]
}, },
"node_modules/@vercel/analytics": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz",
"integrity": "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==",
"license": "MPL-2.0",
"peerDependencies": {
"@remix-run/react": "^2",
"@sveltejs/kit": "^1 || ^2",
"next": ">= 13",
"react": "^18 || ^19 || ^19.0.0-rc",
"svelte": ">= 4",
"vue": "^3",
"vue-router": "^4"
},
"peerDependenciesMeta": {
"@remix-run/react": {
"optional": true
},
"@sveltejs/kit": {
"optional": true
},
"next": {
"optional": true
},
"react": {
"optional": true
},
"svelte": {
"optional": true
},
"vue": {
"optional": true
},
"vue-router": {
"optional": true
}
}
},
"node_modules/abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
"integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/abort-controller": { "node_modules/abort-controller": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -3325,16 +3239,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
"license": "MIT",
"dependencies": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
}
},
"node_modules/conventional-changelog-angular": { "node_modules/conventional-changelog-angular": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz",
@@ -3642,15 +3546,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/define-data-property": { "node_modules/define-data-property": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -3744,59 +3639,6 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/dom-serializer/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.2.7", "version": "3.2.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
@@ -3806,20 +3648,6 @@
"@types/trusted-types": "^2.0.7" "@types/trusted-types": "^2.0.7"
} }
}, },
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dot-prop": { "node_modules/dot-prop": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@@ -3859,48 +3687,6 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/editorconfig": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
"integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
"license": "MIT",
"dependencies": {
"@one-ini/wasm": "0.1.1",
"commander": "^10.0.0",
"minimatch": "9.0.1",
"semver": "^7.5.3"
},
"bin": {
"editorconfig": "bin/editorconfig"
},
"engines": {
"node": ">=14"
}
},
"node_modules/editorconfig/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/editorconfig/node_modules/minimatch": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.234", "version": "1.5.234",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz",
@@ -5597,53 +5383,6 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/html-to-text": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
"integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
"license": "MIT",
"dependencies": {
"@selderee/plugin-htmlparser2": "^0.11.0",
"deepmerge": "^4.3.1",
"dom-serializer": "^2.0.0",
"htmlparser2": "^8.0.2",
"selderee": "^0.11.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/http-proxy-agent": { "node_modules/http-proxy-agent": {
"version": "7.0.2", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
@@ -5797,6 +5536,7 @@
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/internal-slot": { "node_modules/internal-slot": {
@@ -6403,71 +6143,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-beautify": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz",
"integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==",
"license": "MIT",
"dependencies": {
"config-chain": "^1.1.13",
"editorconfig": "^1.0.4",
"glob": "^10.4.2",
"js-cookie": "^3.0.5",
"nopt": "^7.2.1"
},
"bin": {
"css-beautify": "js/bin/css-beautify.js",
"html-beautify": "js/bin/html-beautify.js",
"js-beautify": "js/bin/js-beautify.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/js-beautify/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-beautify/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6650,15 +6325,6 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/leac": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
"integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
"license": "MIT",
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/levn": { "node_modules/levn": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -7509,21 +7175,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/nopt": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
"integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
"license": "ISC",
"dependencies": {
"abbrev": "^2.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/normalize-package-data": { "node_modules/normalize-package-data": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
@@ -7911,19 +7562,6 @@
"url": "https://github.com/inikulin/parse5?sponsor=1" "url": "https://github.com/inikulin/parse5?sponsor=1"
} }
}, },
"node_modules/parseley": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
"integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
"license": "MIT",
"dependencies": {
"leac": "^0.6.0",
"peberminta": "^0.9.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -8004,15 +7642,6 @@
"through": "~2.3" "through": "~2.3"
} }
}, },
"node_modules/peberminta": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
"integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
"license": "MIT",
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -8388,12 +8017,6 @@
"react-is": "^16.13.1" "react-is": "^16.13.1"
} }
}, },
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
"license": "ISC"
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -8487,21 +8110,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-promise-suspense": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz",
"integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^2.0.1"
}
},
"node_modules/react-promise-suspense/node_modules/fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==",
"license": "MIT"
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -8765,18 +8373,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/resend": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/resend/-/resend-3.5.0.tgz",
"integrity": "sha512-bKu4LhXSecP6krvhfDzyDESApYdNfjirD5kykkT1xO0Cj9TKSiGh5Void4pGTs3Am+inSnp4dg0B5XzdwHBJOQ==",
"license": "MIT",
"dependencies": {
"@react-email/render": "0.0.16"
},
"engines": {
"node": ">=18"
}
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.10", "version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -9042,22 +8638,11 @@
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
} }
}, },
"node_modules/selderee": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
"integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
"license": "MIT",
"dependencies": {
"parseley": "^0.12.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.0", "version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
@@ -9073,6 +8658,7 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"yallist": "^4.0.0" "yallist": "^4.0.0"
@@ -10761,6 +10347,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": { "node_modules/yaml": {

View File

@@ -26,19 +26,17 @@
"@prisma/client": "^5.6.0", "@prisma/client": "^5.6.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@vercel/analytics": "^1.1.1",
"openai": "^4.77.0",
"axios": "^1.12.0", "axios": "^1.12.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"isomorphic-dompurify": "^2.15.0", "isomorphic-dompurify": "^2.15.0",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"next": "^15.5.9", "next": "^15.5.9",
"openai": "^4.77.0",
"postcss-nesting": "^12.0.2", "postcss-nesting": "^12.0.2",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.48.2", "react-hook-form": "^7.48.2",
"resend": "^3.1.0",
"tailwind-merge": "^2.1.0", "tailwind-merge": "^2.1.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8" "zod": "^3.23.8"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 B

View File

@@ -1,65 +0,0 @@
import { Resend } from 'resend';
interface EmailTemplate {
subject: string;
template: JSX.Element;
}
export async function sender(
recipients: string[],
{ subject, template }: EmailTemplate
) {
if (!process.env.RESEND_KEY) {
throw new Error('RESEND_KEY is not set');
}
if (recipients.length === 0) {
console.info(`${subject} email skipped for having zero recipients`);
return true;
}
const resend = new Resend(process.env.RESEND_KEY);
try {
let response;
if (recipients.length == 1) {
response = await resend.emails.send({
from: process.env.RESEND_FROM!,
to: recipients[0],
subject,
react: template,
headers: {
'List-Unsubscribe': `<${process.env.HOME_URL}/unsubscribe>`
}
});
} else {
response = await resend.batch.send(
recipients.map(recipient => {
return {
from: process.env.RESEND_FROM!,
to: recipient,
subject,
react: template,
headers: {
'List-Unsubscribe': `<${process.env.HOME_URL}/unsubscribe>`
}
};
})
);
}
const { error } = response;
if (error) {
console.error(error);
return false;
}
console.info(`${subject} email sent to ${recipients.length} recipients`);
return true;
} catch (error) {
console.error(error);
return false;
}
}

90
utils/sweego.ts Normal file
View File

@@ -0,0 +1,90 @@
interface EmailTemplate {
subject: string;
template: JSX.Element;
}
const SWEEGO_API_URL = 'https://api.sweego.io/send';
const renderTemplate = async (template: JSX.Element): Promise<string> => {
const { renderToStaticMarkup } = await import('react-dom/server');
const html = renderToStaticMarkup(template);
return `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: 0; padding: 0; background-color: #f4f4f5;">
${html}
</body>
</html>`;
};
export async function sender(
recipients: string[],
{ subject, template }: EmailTemplate
): Promise<boolean> {
if (!process.env.SWEEGO_API_KEY) {
throw new Error('SWEEGO_API_KEY is not set');
}
if (!process.env.SWEEGO_FROM) {
throw new Error('SWEEGO_FROM is not set');
}
if (recipients.length === 0) {
console.info(`${subject} email skipped for having zero recipients`);
return true;
}
const htmlContent = await renderTemplate(template);
const fromName = process.env.NEXT_PUBLIC_BRAND_NAME || 'Newsletter';
let successCount = 0;
let failCount = 0;
for (const recipient of recipients) {
try {
const response = await fetch(SWEEGO_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Api-Key': process.env.SWEEGO_API_KEY
},
body: JSON.stringify({
channel: 'email',
provider: 'sweego',
recipients: [{ email: recipient }],
from: {
name: fromName,
email: process.env.SWEEGO_FROM
},
subject,
'message-html': htmlContent,
headers: {
'List-Unsubscribe': `<mailto:${process.env.NEXT_PUBLIC_BRAND_EMAIL}>`
}
})
});
if (!response.ok) {
const error = await response.text();
console.error(
`Failed to send to ${recipient}: ${response.status} ${error}`
);
failCount++;
continue;
}
successCount++;
} catch (error) {
console.error(`Failed to send to ${recipient}:`, error);
failCount++;
}
}
console.info(
`${subject} email: ${successCount} sent, ${failCount} failed out of ${recipients.length} recipients`
);
return successCount > 0;
}

220
yarn.lock
View File

@@ -599,11 +599,6 @@
resolved "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz" resolved "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz"
integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
"@one-ini/wasm@0.1.1":
version "0.1.1"
resolved "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz"
integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==
"@pkgjs/parseargs@^0.11.0": "@pkgjs/parseargs@^0.11.0":
version "0.11.0" version "0.11.0"
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
@@ -681,15 +676,6 @@
dependencies: dependencies:
"@radix-ui/react-compose-refs" "1.1.2" "@radix-ui/react-compose-refs" "1.1.2"
"@react-email/render@0.0.16":
version "0.0.16"
resolved "https://registry.npmjs.org/@react-email/render/-/render-0.0.16.tgz"
integrity sha512-wDaMy27xAq1cJHtSFptp0DTKPuV2GYhloqia95ub/DH9Dea1aWYsbdM918MOc/b/HvVS3w1z8DWzfAk13bGStQ==
dependencies:
html-to-text "9.0.5"
js-beautify "^1.14.11"
react-promise-suspense "0.3.4"
"@rtsao/scc@^1.1.0": "@rtsao/scc@^1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz" resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz"
@@ -700,14 +686,6 @@
resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.13.0.tgz" resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.13.0.tgz"
integrity sha512-2ih5qGw5SZJ+2fLZxP6Lr6Na2NTIgPRL/7Kmyuw0uIyBQnuhQ8fi8fzUTd38eIQmqp+GYLC00cI6WgtqHxBwmw== integrity sha512-2ih5qGw5SZJ+2fLZxP6Lr6Na2NTIgPRL/7Kmyuw0uIyBQnuhQ8fi8fzUTd38eIQmqp+GYLC00cI6WgtqHxBwmw==
"@selderee/plugin-htmlparser2@^0.11.0":
version "0.11.0"
resolved "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz"
integrity sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==
dependencies:
domhandler "^5.0.3"
selderee "^0.11.0"
"@swc/helpers@0.5.15": "@swc/helpers@0.5.15":
version "0.5.15" version "0.5.15"
resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz" resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz"
@@ -980,16 +958,6 @@
resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz" resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz"
integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==
"@vercel/analytics@^1.1.1":
version "1.5.0"
resolved "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz"
integrity sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==
abbrev@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz"
integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==
abort-controller@^3.0.0: abort-controller@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz"
@@ -1489,11 +1457,6 @@ combined-stream@^1.0.8:
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
commander@^10.0.0:
version "10.0.1"
resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz"
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
commander@^13.1.0: commander@^13.1.0:
version "13.1.0" version "13.1.0"
resolved "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz" resolved "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz"
@@ -1522,14 +1485,6 @@ concat-map@0.0.1:
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
config-chain@^1.1.13:
version "1.1.13"
resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz"
integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
dependencies:
ini "^1.3.4"
proto-list "~1.2.1"
conventional-changelog-angular@^7.0.0: conventional-changelog-angular@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz" resolved "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz"
@@ -1694,11 +1649,6 @@ deep-is@^0.1.3:
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
deepmerge@^4.3.1:
version "4.3.1"
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
define-data-property@^1.0.1, define-data-property@^1.1.4: define-data-property@^1.0.1, define-data-property@^1.1.4:
version "1.1.4" version "1.1.4"
resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz"
@@ -1758,27 +1708,6 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz"
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
entities "^4.2.0"
domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3"
resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
domelementtype "^2.3.0"
dompurify@^3.2.7: dompurify@^3.2.7:
version "3.2.7" version "3.2.7"
resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz" resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz"
@@ -1786,15 +1715,6 @@ dompurify@^3.2.7:
optionalDependencies: optionalDependencies:
"@types/trusted-types" "^2.0.7" "@types/trusted-types" "^2.0.7"
domutils@^3.0.1:
version "3.2.2"
resolved "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz"
integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
dependencies:
dom-serializer "^2.0.0"
domelementtype "^2.3.0"
domhandler "^5.0.3"
dot-prop@^5.1.0: dot-prop@^5.1.0:
version "5.3.0" version "5.3.0"
resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz"
@@ -1821,16 +1741,6 @@ eastasianwidth@^0.2.0:
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
editorconfig@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz"
integrity sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==
dependencies:
"@one-ini/wasm" "0.1.1"
commander "^10.0.0"
minimatch "9.0.1"
semver "^7.5.3"
electron-to-chromium@^1.5.227: electron-to-chromium@^1.5.227:
version "1.5.234" version "1.5.234"
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz"
@@ -1851,16 +1761,6 @@ emoji-regex@^9.2.2:
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
entities@^4.2.0:
version "4.5.0"
resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
entities@^4.4.0:
version "4.5.0"
resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
entities@^6.0.0: entities@^6.0.0:
version "6.0.1" version "6.0.1"
resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz" resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz"
@@ -2280,11 +2180,6 @@ execa@^8.0.1:
signal-exit "^4.1.0" signal-exit "^4.1.0"
strip-final-newline "^3.0.0" strip-final-newline "^3.0.0"
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz"
integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
@@ -2567,18 +2462,6 @@ glob@^10.3.10:
package-json-from-dist "^1.0.0" package-json-from-dist "^1.0.0"
path-scurry "^1.11.1" path-scurry "^1.11.1"
glob@^10.4.2:
version "10.4.5"
resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz"
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
dependencies:
foreground-child "^3.1.0"
jackspeak "^3.1.2"
minimatch "^9.0.4"
minipass "^7.1.2"
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
glob@^7.1.3, glob@7.1.7: glob@^7.1.3, glob@7.1.7:
version "7.1.7" version "7.1.7"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz"
@@ -2714,27 +2597,6 @@ html-escaper@^2.0.2:
resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
html-to-text@9.0.5:
version "9.0.5"
resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz"
integrity sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==
dependencies:
"@selderee/plugin-htmlparser2" "^0.11.0"
deepmerge "^4.3.1"
dom-serializer "^2.0.0"
htmlparser2 "^8.0.2"
selderee "^0.11.0"
htmlparser2@^8.0.2:
version "8.0.2"
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.3"
domutils "^3.0.1"
entities "^4.4.0"
http-proxy-agent@^7.0.2: http-proxy-agent@^7.0.2:
version "7.0.2" version "7.0.2"
resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz"
@@ -3140,22 +3002,6 @@ jju@^1.4.0:
resolved "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz" resolved "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz"
integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==
js-beautify@^1.14.11:
version "1.15.4"
resolved "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz"
integrity sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==
dependencies:
config-chain "^1.1.13"
editorconfig "^1.0.4"
glob "^10.4.2"
js-cookie "^3.0.5"
nopt "^7.2.1"
js-cookie@^3.0.5:
version "3.0.5"
resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz"
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
@@ -3273,11 +3119,6 @@ language-tags@^1.0.9:
dependencies: dependencies:
language-subtag-registry "^0.3.20" language-subtag-registry "^0.3.20"
leac@^0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz"
integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
levn@^0.4.1: levn@^0.4.1:
version "0.4.1" version "0.4.1"
resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
@@ -3558,13 +3399,6 @@ minimatch@^9.0.4:
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
minimatch@9.0.1:
version "9.0.1"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz"
integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==
dependencies:
brace-expansion "^2.0.1"
minimatch@9.0.3: minimatch@9.0.3:
version "9.0.3" version "9.0.3"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz"
@@ -3625,7 +3459,7 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
next@^15.5.9, "next@>= 13": next@^15.5.9:
version "15.5.9" version "15.5.9"
resolved "https://registry.npmjs.org/next/-/next-15.5.9.tgz" resolved "https://registry.npmjs.org/next/-/next-15.5.9.tgz"
integrity sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg== integrity sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==
@@ -3663,13 +3497,6 @@ node-releases@^2.0.21:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz"
integrity sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg== integrity sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==
nopt@^7.2.1:
version "7.2.1"
resolved "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz"
integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==
dependencies:
abbrev "^2.0.0"
normalize-package-data@^2.5.0: normalize-package-data@^2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz"
@@ -3914,14 +3741,6 @@ parse5@^7.3.0:
dependencies: dependencies:
entities "^6.0.0" entities "^6.0.0"
parseley@^0.12.0:
version "0.12.1"
resolved "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz"
integrity sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==
dependencies:
leac "^0.6.0"
peberminta "^0.9.0"
path-exists@^4.0.0: path-exists@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
@@ -3967,11 +3786,6 @@ pause-stream@^0.0.11:
dependencies: dependencies:
through "~2.3" through "~2.3"
peberminta@^0.9.0:
version "0.9.0"
resolved "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz"
integrity sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==
picocolors@^1.0.0, picocolors@^1.1.1: picocolors@^1.0.0, picocolors@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
@@ -4110,11 +3924,6 @@ prop-types@^15.8.1:
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.13.1" react-is "^16.13.1"
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz"
integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
proxy-from-env@^1.1.0: proxy-from-env@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
@@ -4135,7 +3944,7 @@ quick-lru@^4.0.1:
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz" resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
"react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", react-dom@^18, react-dom@^18.2.0, "react-dom@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0": "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", react-dom@^18, "react-dom@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0":
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@@ -4153,14 +3962,7 @@ react-is@^16.13.1:
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-promise-suspense@0.3.4: "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", react@^18, "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0":
version "0.3.4"
resolved "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz"
integrity sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==
dependencies:
fast-deep-equal "^2.0.1"
"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", react@^18, "react@^18 || ^19 || ^19.0.0-rc", react@^18.2.0, "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0":
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
@@ -4258,13 +4060,6 @@ require-from-string@^2.0.2:
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
resend@^3.1.0:
version "3.5.0"
resolved "https://registry.npmjs.org/resend/-/resend-3.5.0.tgz"
integrity sha512-bKu4LhXSecP6krvhfDzyDESApYdNfjirD5kykkT1xO0Cj9TKSiGh5Void4pGTs3Am+inSnp4dg0B5XzdwHBJOQ==
dependencies:
"@react-email/render" "0.0.16"
resolve-from@^4.0.0: resolve-from@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
@@ -4394,19 +4189,12 @@ scheduler@^0.23.2:
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
selderee@^0.11.0:
version "0.11.0"
resolved "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz"
integrity sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==
dependencies:
parseley "^0.12.0"
semver@^6.3.1: semver@^6.3.1:
version "6.3.1" version "6.3.1"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.0.0, semver@^7.3.4, semver@^7.5.3, semver@^7.5.4, semver@7.6.0: semver@^7.0.0, semver@^7.3.4, semver@^7.5.4, semver@7.6.0:
version "7.6.0" version "7.6.0"
resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz" resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz"
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==