Compare commits
8 Commits
feature/se
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5362ff7599 | |||
| 21b3c3ba83 | |||
| 44b9793c60 | |||
| 35020f2499 | |||
| 20b09849bc | |||
| dc3850ac4d | |||
| e73262b3b3 | |||
| 7299a266f1 |
@@ -1,6 +1,6 @@
|
||||
ADMIN_EMAIL=""
|
||||
CRON_SECRET=""
|
||||
HOME_URL=""
|
||||
NEXT_PUBLIC_HOME_URL=""
|
||||
OVHCLOUD_API_KEY=""
|
||||
MAINTENANCE_MODE="0"
|
||||
NEWS_LIMIT="50"
|
||||
@@ -8,7 +8,8 @@ NEWS_TO_USE="10"
|
||||
NEXT_PUBLIC_BRAND_COUNTRY=""
|
||||
NEXT_PUBLIC_BRAND_EMAIL=""
|
||||
NEXT_PUBLIC_BRAND_NAME=""
|
||||
NEXT_PUBLIC_BRAND_OWNER_NAME=""
|
||||
DATABASE_URL=""
|
||||
RESEND_FROM=""
|
||||
RESEND_KEY=""
|
||||
SWEEGO_API_KEY=""
|
||||
SWEEGO_FROM=""
|
||||
SECRET_HASH=""
|
||||
|
||||
@@ -36,6 +36,6 @@ jobs:
|
||||
ssh debian@51.210.247.57 << 'EOF'
|
||||
cd /home/debian/newsletter-hackernews
|
||||
git pull origin main
|
||||
cd /home/debian/gitea
|
||||
cd /home/debian/infrastructure
|
||||
docker compose up -d --build newsletter
|
||||
EOF
|
||||
|
||||
@@ -11,10 +11,14 @@ COPY . .
|
||||
ARG NEXT_PUBLIC_BRAND_NAME
|
||||
ARG NEXT_PUBLIC_BRAND_EMAIL
|
||||
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_EMAIL=$NEXT_PUBLIC_BRAND_EMAIL
|
||||
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
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ export const dynamic = 'force-dynamic'; // defaults to force-static
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
if (!process.env.RESEND_KEY) {
|
||||
throw new Error('Resend variables not set');
|
||||
if (!process.env.SWEEGO_API_KEY) {
|
||||
throw new Error('SWEEGO_API_KEY is not set');
|
||||
}
|
||||
const body = await request.json();
|
||||
const validation = ConfirmationSchema.safeParse(body);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NewsletterTemplate } from '@components/email/Newsletter';
|
||||
import prisma from '@prisma/prisma';
|
||||
import { formatApiResponse } from '@utils/formatApiResponse';
|
||||
import { sender } from '@utils/resendClient';
|
||||
import { sender } from '@utils/sweego';
|
||||
import {
|
||||
INTERNAL_SERVER_ERROR,
|
||||
STATUS_INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ConfirmationTemplate } from '@components/email/Confirmation';
|
||||
import prisma from '@prisma/prisma';
|
||||
import { formatApiResponse } from '@utils/formatApiResponse';
|
||||
import { sender } from '@utils/resendClient';
|
||||
import { sender } from '@utils/sweego';
|
||||
import {
|
||||
BAD_REQUEST,
|
||||
INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UnsubscribeTemplate } from '@components/email/Unsubscribe';
|
||||
import prisma from '@prisma/prisma';
|
||||
import { formatApiResponse } from '@utils/formatApiResponse';
|
||||
import { sender } from '@utils/resendClient';
|
||||
import { sender } from '@utils/sweego';
|
||||
import {
|
||||
BAD_REQUEST,
|
||||
INTERNAL_SERVER_ERROR,
|
||||
@@ -30,13 +30,10 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
});
|
||||
|
||||
if (user && !user.deleted) {
|
||||
await prisma.user.update({
|
||||
if (user) {
|
||||
await prisma.user.delete({
|
||||
where: {
|
||||
email
|
||||
},
|
||||
data: {
|
||||
deleted: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ const Confirmation = () => {
|
||||
'@type': 'WebSite',
|
||||
name: 'Hackernews Newsletter',
|
||||
title: 'Subscription Confirmation',
|
||||
url: `${process.env.HOME_URL}/confirmation`
|
||||
url: `${process.env.NEXT_PUBLIC_HOME_URL}/confirmation`
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Tiles } from '@components/tiles/Tiles';
|
||||
import { cn } from '@utils/cn';
|
||||
import { Analytics } from '@vercel/analytics/react';
|
||||
import type { Metadata } from 'next';
|
||||
import { Inter as FontSans } from 'next/font/google';
|
||||
import Script from 'next/script';
|
||||
import './globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -34,8 +34,12 @@ export default function RootLayout({
|
||||
<Tiles>
|
||||
<div className='z-10'>{children}</div>
|
||||
</Tiles>
|
||||
<Analytics />
|
||||
</body>
|
||||
<Script
|
||||
defer
|
||||
src='https://analytics.frompixels.com/script.js'
|
||||
data-website-id='588e7b7d-e9cd-4b96-94bf-8269c499b0a2'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const Home = () => {
|
||||
'@type': 'WebSite',
|
||||
name: 'Hackernews Newsletter',
|
||||
title: 'Home',
|
||||
url: process.env.HOME_URL
|
||||
url: process.env.NEXT_PUBLIC_HOME_URL
|
||||
};
|
||||
|
||||
const form = useForm<SubscribeFormType>({
|
||||
|
||||
@@ -2,458 +2,113 @@
|
||||
|
||||
import { CustomCard } from '@components/CustomCard';
|
||||
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 email = useObfuscatedEmail();
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'Hackernews Newsletter',
|
||||
title: 'Privacy Policy',
|
||||
url: `${process.env.HOME_URL}/privacy`
|
||||
url: `${process.env.NEXT_PUBLIC_HOME_URL}/privacy`
|
||||
};
|
||||
|
||||
const body = (
|
||||
<div className='privacy-content my-2 max-h-[50vh] space-y-1 overflow-auto'>
|
||||
<h2>Who We Are</h2>
|
||||
<p className='leading-relaxed'>
|
||||
This Privacy Policy describes Our policies and procedures on the
|
||||
collection, use and disclosure of Your information when You use the
|
||||
Service and tells You about Your privacy rights and how the law protects
|
||||
You.
|
||||
Data controller: {process.env.NEXT_PUBLIC_BRAND_OWNER_NAME}, an
|
||||
individual based in {process.env.NEXT_PUBLIC_BRAND_COUNTRY}.
|
||||
</p>
|
||||
<p>
|
||||
We use Your Personal data to provide and improve the Service. By using
|
||||
the Service, You agree to the collection and use of information in
|
||||
accordance with this Privacy Policy.
|
||||
Contact:{' '}
|
||||
<a
|
||||
href={`mailto:${process.env.NEXT_PUBLIC_BRAND_EMAIL}`}
|
||||
className='text-purple-600 hover:text-purple-700'
|
||||
>
|
||||
{process.env.NEXT_PUBLIC_BRAND_EMAIL}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h2>Interpretation and Definitions</h2>
|
||||
<h3>Interpretation</h3>
|
||||
<h2>What We Collect</h2>
|
||||
<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.
|
||||
Your email address (required to send the newsletter).
|
||||
</p>
|
||||
|
||||
<h3>Definitions</h3>
|
||||
<h2>Why We Collect It</h2>
|
||||
<p className='leading-relaxed'>
|
||||
For the purposes of this Privacy Policy:
|
||||
To deliver the daily {process.env.NEXT_PUBLIC_BRAND_NAME}{' '}
|
||||
newsletter—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>
|
||||
<p>
|
||||
<strong>Account</strong> means a unique account created for You to
|
||||
access our Service or parts of our Service.
|
||||
</p>
|
||||
<strong>Email delivery:</strong> Resend (US). GDPR-compliant email
|
||||
infrastructure.
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Affiliate</strong> means an entity that controls, is
|
||||
controlled by or is under common control with a party, where
|
||||
"control" 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>
|
||||
<strong>Hosting & analytics:</strong> Vercel (US). Analytics are
|
||||
anonymized and aggregated—no personal data beyond your email is
|
||||
collected.
|
||||
</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 "the
|
||||
Company", "We", "Us" or "Our" 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>
|
||||
<strong>Content source:</strong> Public Hacker News API (Y Combinator,
|
||||
US).
|
||||
</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>
|
||||
<h2>Your Rights</h2>
|
||||
<ul className='list-disc space-y-4 pl-6'>
|
||||
<li>Unsubscribe anytime via the link in every email</li>
|
||||
<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'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'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'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'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'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'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 "Last
|
||||
updated" 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 ? (
|
||||
Request deletion of your email by contacting{' '}
|
||||
<a
|
||||
href={`mailto:${email}`}
|
||||
href={`mailto:${process.env.NEXT_PUBLIC_BRAND_EMAIL}`}
|
||||
className='text-purple-600 hover:text-purple-700'
|
||||
>
|
||||
{email}
|
||||
{process.env.NEXT_PUBLIC_BRAND_EMAIL}
|
||||
</a>
|
||||
) : (
|
||||
<span className='text-gray-400'>loading...</span>
|
||||
)}
|
||||
</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>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
@@ -465,7 +120,7 @@ const Privacy = () => {
|
||||
<CustomCard
|
||||
className='max-90vh max-90vw'
|
||||
title='Privacy Policy'
|
||||
description='Last updated: November 23, 2024'
|
||||
description='Last updated: January 28, 2026'
|
||||
content={body}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -6,6 +6,6 @@ export default function robots(): MetadataRoute.Robots {
|
||||
userAgent: '*',
|
||||
disallow: ''
|
||||
},
|
||||
sitemap: `${process.env.HOME_URL!}/sitemap.xml`
|
||||
sitemap: `${process.env.NEXT_PUBLIC_HOME_URL!}/sitemap.xml`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,19 +3,19 @@ import { MetadataRoute } from 'next';
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: process.env.HOME_URL!,
|
||||
url: process.env.NEXT_PUBLIC_HOME_URL!,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 1
|
||||
},
|
||||
{
|
||||
url: `${process.env.HOME_URL!}/privacy`,
|
||||
url: `${process.env.NEXT_PUBLIC_HOME_URL!}/privacy`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.5
|
||||
},
|
||||
{
|
||||
url: `${process.env.HOME_URL!}/unsubscribe`,
|
||||
url: `${process.env.NEXT_PUBLIC_HOME_URL!}/unsubscribe`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.2
|
||||
|
||||
@@ -33,7 +33,7 @@ const Unsubscribe = () => {
|
||||
'@type': 'WebSite',
|
||||
name: 'Hackernews Newsletter',
|
||||
title: 'Unsubscribe',
|
||||
url: `${process.env.HOME_URL}/unsubscribe`
|
||||
url: `${process.env.NEXT_PUBLIC_HOME_URL}/unsubscribe`
|
||||
};
|
||||
|
||||
const form = useForm<UnsubscribeFormType>({
|
||||
|
||||
@@ -22,7 +22,7 @@ export const ConfirmationTemplate = (code: string) => {
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href={`${process.env.HOME_URL}/confirmation?code=${code}`}
|
||||
href={`${process.env.NEXT_PUBLIC_HOME_URL}/confirmation?code=${code}`}
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '12px 24px',
|
||||
|
||||
@@ -22,7 +22,7 @@ export const UnsubscribeTemplate = () => {
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href={`${process.env.HOME_URL}/`}
|
||||
href={`${process.env.NEXT_PUBLIC_HOME_URL}/`}
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '12px 24px',
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
const iconStyle = {
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
|
||||
const Icon = ({ name, size = 16 }: { name: string; size?: number }) => (
|
||||
<img
|
||||
src={`${process.env.HOME_URL}/email-icons/${name}.png`}
|
||||
width={size}
|
||||
height={size}
|
||||
alt=""
|
||||
style={iconStyle}
|
||||
/>
|
||||
);
|
||||
import {
|
||||
User,
|
||||
Building2,
|
||||
Mail,
|
||||
LogOut,
|
||||
LayoutGrid,
|
||||
Shield,
|
||||
Home
|
||||
} from 'lucide-react';
|
||||
|
||||
export const Footer = () => {
|
||||
return (
|
||||
@@ -49,7 +44,7 @@ export const Footer = () => {
|
||||
letterSpacing: '0.05em'
|
||||
}}
|
||||
>
|
||||
<Icon name="user" size={16} />
|
||||
<User size={16} color='#386FA4' />
|
||||
Contact Us
|
||||
</h4>
|
||||
<p
|
||||
@@ -62,7 +57,7 @@ export const Footer = () => {
|
||||
color: '#4A5568'
|
||||
}}
|
||||
>
|
||||
<Icon name="building-2" size={14} />
|
||||
<Building2 size={14} color='#386FA4' />
|
||||
{process.env.NEXT_PUBLIC_BRAND_NAME}
|
||||
</p>
|
||||
<p
|
||||
@@ -75,7 +70,7 @@ export const Footer = () => {
|
||||
color: '#4A5568'
|
||||
}}
|
||||
>
|
||||
<Icon name="mail" size={14} />
|
||||
<Mail size={14} color='#386FA4' />
|
||||
<a
|
||||
href={`mailto:${process.env.NEXT_PUBLIC_BRAND_EMAIL}`}
|
||||
style={{ color: '#386FA4', textDecoration: 'none' }}
|
||||
@@ -93,11 +88,11 @@ export const Footer = () => {
|
||||
color: '#4A5568'
|
||||
}}
|
||||
>
|
||||
<Icon name="log-out" size={14} />
|
||||
<LogOut size={14} color='#386FA4' />
|
||||
<span>
|
||||
Click{' '}
|
||||
<a
|
||||
href={`${process.env.HOME_URL}/unsubscribe`}
|
||||
href={`${process.env.NEXT_PUBLIC_HOME_URL}/unsubscribe`}
|
||||
style={{ color: '#386FA4', textDecoration: 'none' }}
|
||||
>
|
||||
here
|
||||
@@ -129,7 +124,7 @@ export const Footer = () => {
|
||||
letterSpacing: '0.05em'
|
||||
}}
|
||||
>
|
||||
<Icon name="layout-grid" size={16} />
|
||||
<LayoutGrid size={16} color='#386FA4' />
|
||||
Quick Links
|
||||
</h4>
|
||||
<p
|
||||
@@ -142,9 +137,9 @@ export const Footer = () => {
|
||||
color: '#4A5568'
|
||||
}}
|
||||
>
|
||||
<Icon name="shield" size={14} />
|
||||
<Shield size={14} color='#386FA4' />
|
||||
<a
|
||||
href={`${process.env.HOME_URL}/privacy`}
|
||||
href={`${process.env.NEXT_PUBLIC_HOME_URL}/privacy`}
|
||||
style={{ color: '#386FA4', textDecoration: 'none' }}
|
||||
>
|
||||
Privacy Policy
|
||||
@@ -160,9 +155,9 @@ export const Footer = () => {
|
||||
color: '#4A5568'
|
||||
}}
|
||||
>
|
||||
<Icon name="house" size={14} />
|
||||
<Home size={14} color='#386FA4' />
|
||||
<a
|
||||
href={process.env.HOME_URL}
|
||||
href={process.env.NEXT_PUBLIC_HOME_URL}
|
||||
style={{ color: '#386FA4', textDecoration: 'none' }}
|
||||
>
|
||||
Visit Website
|
||||
|
||||
421
package-lock.json
generated
@@ -13,7 +13,6 @@
|
||||
"@prisma/client": "^5.6.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@vercel/analytics": "^1.1.1",
|
||||
"axios": "^1.12.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
@@ -25,7 +24,6 @@
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.48.2",
|
||||
"resend": "^3.1.0",
|
||||
"tailwind-merge": "^2.1.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.23.8"
|
||||
@@ -1546,12 +1544,6 @@
|
||||
"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": {
|
||||
"version": "0.11.0",
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||
@@ -1747,19 +1721,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
@@ -2343,53 +2304,6 @@
|
||||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
@@ -3325,16 +3239,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz",
|
||||
@@ -3642,15 +3546,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
@@ -3744,59 +3639,6 @@
|
||||
"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": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
||||
@@ -3806,20 +3648,6 @@
|
||||
"@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": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
|
||||
@@ -3859,48 +3687,6 @@
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"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": {
|
||||
"version": "1.5.234",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz",
|
||||
@@ -5597,53 +5383,6 @@
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"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": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||
@@ -5797,6 +5536,7 @@
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
@@ -6403,71 +6143,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -6650,15 +6325,6 @@
|
||||
"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": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
@@ -7509,21 +7175,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "3.0.3",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@@ -8004,15 +7642,6 @@
|
||||
"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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -8388,12 +8017,6 @@
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
@@ -8487,21 +8110,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -8765,18 +8373,6 @@
|
||||
"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": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
@@ -9042,22 +8638,11 @@
|
||||
"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": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
@@ -9073,6 +8658,7 @@
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
@@ -10761,6 +10347,7 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
|
||||
@@ -26,19 +26,17 @@
|
||||
"@prisma/client": "^5.6.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@vercel/analytics": "^1.1.1",
|
||||
"openai": "^4.77.0",
|
||||
"axios": "^1.12.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"isomorphic-dompurify": "^2.15.0",
|
||||
"lucide-react": "^0.460.0",
|
||||
"next": "^15.5.9",
|
||||
"openai": "^4.77.0",
|
||||
"postcss-nesting": "^12.0.2",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.48.2",
|
||||
"resend": "^3.1.0",
|
||||
"tailwind-merge": "^2.1.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.23.8"
|
||||
|
||||
|
Before Width: | Height: | Size: 397 B |
|
Before Width: | Height: | Size: 416 B |
|
Before Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 328 B |
|
Before Width: | Height: | Size: 414 B |
|
Before Width: | Height: | Size: 480 B |
|
Before Width: | Height: | Size: 394 B |
@@ -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
@@ -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
@@ -599,11 +599,6 @@
|
||||
resolved "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz"
|
||||
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":
|
||||
version "0.11.0"
|
||||
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
|
||||
@@ -681,15 +676,6 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "1.1.0"
|
||||
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"
|
||||
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":
|
||||
version "0.5.15"
|
||||
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"
|
||||
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:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz"
|
||||
@@ -1489,11 +1457,6 @@ combined-stream@^1.0.8:
|
||||
dependencies:
|
||||
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:
|
||||
version "13.1.0"
|
||||
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"
|
||||
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:
|
||||
version "7.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz"
|
||||
@@ -1758,27 +1708,6 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz"
|
||||
@@ -1786,15 +1715,6 @@ dompurify@^3.2.7:
|
||||
optionalDependencies:
|
||||
"@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:
|
||||
version "5.3.0"
|
||||
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"
|
||||
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:
|
||||
version "1.5.234"
|
||||
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"
|
||||
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:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz"
|
||||
@@ -2280,11 +2180,6 @@ execa@^8.0.1:
|
||||
signal-exit "^4.1.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:
|
||||
version "3.1.3"
|
||||
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"
|
||||
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:
|
||||
version "7.1.7"
|
||||
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"
|
||||
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:
|
||||
version "7.0.2"
|
||||
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"
|
||||
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:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
||||
@@ -3273,11 +3119,6 @@ language-tags@^1.0.9:
|
||||
dependencies:
|
||||
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:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
|
||||
@@ -3558,13 +3399,6 @@ minimatch@^9.0.4:
|
||||
dependencies:
|
||||
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:
|
||||
version "9.0.3"
|
||||
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"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
next@^15.5.9, "next@>= 13":
|
||||
next@^15.5.9:
|
||||
version "15.5.9"
|
||||
resolved "https://registry.npmjs.org/next/-/next-15.5.9.tgz"
|
||||
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"
|
||||
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:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz"
|
||||
@@ -3914,14 +3741,6 @@ parse5@^7.3.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
|
||||
@@ -3967,11 +3786,6 @@ pause-stream@^0.0.11:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.1.1"
|
||||
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"
|
||||
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:
|
||||
version "1.1.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
|
||||
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"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-promise-suspense@0.3.4:
|
||||
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":
|
||||
"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 "18.3.1"
|
||||
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
|
||||
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"
|
||||
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:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
|
||||
@@ -4394,19 +4189,12 @@ scheduler@^0.23.2:
|
||||
dependencies:
|
||||
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:
|
||||
version "6.3.1"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
|
||||
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"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz"
|
||||
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
|
||||
|
||||