Skip to content

Commit

Permalink
Merge pull request #4 from simonyiszk/feature/newsletter-init
Browse files Browse the repository at this point in the history
Newsletter subscription
  • Loading branch information
Tschonti authored Dec 16, 2023
2 parents e6284ab + b8cdf75 commit 230d17d
Show file tree
Hide file tree
Showing 14 changed files with 802 additions and 413 deletions.
4 changes: 4 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SERVICE_ACCOUNT_JSON="<base-64 encoded creadentials.json of the service account>"
SERVICE_ACCOUNT_SUBJECT="<subject of the service account>"
JWT_SCOPE="<required scope for the service account>"
GROUP_KEY="<email address of the google group>"
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
"url": "https://simonyi.bme.hu"
},
"dependencies": {
"@headlessui/react": "^1.7.17",
"clsx": "^2.0.0",
"email-validator": "^2.0.4",
"googleapis": "^129.0.0",
"next": "14.0.3",
"react": "^18",
"react-dom": "^18",
Expand Down
36 changes: 36 additions & 0 deletions src/app/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use server';
import { google } from 'googleapis';

export async function addToGroup({ email }: { email: string }) {
if (!email) {
return 400;
}
const credentials = JSON.parse(Buffer.from(process.env.SERVICE_ACCOUNT_JSON ?? '', 'base64').toString());
const auth = new google.auth.JWT({
email: credentials.client_email,
key: credentials.private_key,
subject: process.env.SERVICE_ACCOUNT_SUBJECT,
scopes: [process.env.JWT_SCOPE ?? ''],
});

try {
await google.admin({ version: 'directory_v1', auth }).members.insert({
groupKey: process.env.GROUP_KEY,
requestBody: {
email: email,
role: 'MEMBER',
},
});
return 200;
} catch (e) {
if ((e as any).response?.data?.error.code === 409) {
return 409;
}
if ((e as any).response?.data?.error.code === 400) {
return 400;
}
console.log(e);
console.log('error: ', (e as any).response?.data?.error);
return 500;
}
}
3 changes: 1 addition & 2 deletions src/app/contact/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ export default function Contact() {
return (
<div className='flex flex-col'>
<p className='text-4xl font-bold mb-20 text-center'>Vállalati és sajtómegkeresések, általános kérdések:</p>
<p className='text-2xl font-normal mb-20 text-center'>lorem ipsum?</p>
<Link
href='mailto:[email protected]'
className='text-2xl font-semibold text-center hover:text-brand'
className='text-2xl md:text-3xl font-semibold text-center hover:text-brand break-all'
target='blank'
>
[email protected]
Expand Down
6 changes: 2 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<html lang='hu'>
<body className={raleway.className}>
<main className='grid grid-rows-[auto_1fr_auto] h-full'>
<main className='flex flex-col min-h-screen'>
<Navbar />
<div className='max-w-6xl p-20 mx-auto relative h-full w-full flex justify-center items-center'>
{children}
</div>
<div className='flex-grow relative flex flex-col justify-center items-center'>{children}</div>
<Footer />
</main>
</body>
Expand Down
24 changes: 16 additions & 8 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import Image from 'next/image';

import { metadata } from '@/app/layout';
import { SocialButtons } from '@/components/footer/social-buttons';
import { NewsletterModals } from '@/components/newsletter/newsletter-modals';

import konfLogo from '../../public/img/konf.svg';

export default function Landing() {
return (
<>
<div className='shadow-gloria rounded-full overflow-hidden'>
<video className='h-full w-full' autoPlay playsInline loop controls={false}>
<source src='/video/nebula.mp4' type='video/mp4' />
</video>
<div className='p-10 relative'>
<div className='max-w-md md:max-w-xl relative shadow-gloria rounded-full overflow-hidden mx-auto'>
<video className='h-full w-full' autoPlay playsInline loop muted>
<source src='/video/nebula.mp4' type='video/mp4' />
</video>
</div>
<div className='absolute top-0 bottom-0 left-0 right-0 flex items-center justify-center flex-col gap-5 p-5'>
<Image src={konfLogo} alt='Simonyi Konferencia' className='w-full' />
<p className='font-bold text-xl sm:text-2xl text-center'>{metadata.description}</p>
<p className='font-semibold text-4xl sm:text-6xl'>24. 03. 19.</p>
</div>
</div>
<div className='absolute top-0 bottom-0 left-0 right-0 flex items-center justify-center flex-col py-5 gap-5 p-5'>
<Image src={konfLogo} alt='Simonyi Konferencia' className='w-full' />
<p className='font-bold text-xl sm:text-2xl text-center'>{metadata.description}</p>
<p className='font-semibold text-2xl sm:text-6xl'>24. 03. 19.</p>
<div className='flex md:hidden flex-col items-center gap-10 mt-10'>
<SocialButtons />
<NewsletterModals />
</div>
</>
);
Expand Down
21 changes: 6 additions & 15 deletions src/components/footer/desktop-footer.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import Image from 'next/image';
import Link from 'next/link';
import { FaFacebook, FaInstagram, FaYoutube } from 'react-icons/fa';

import { NewsletterModals } from '../newsletter/newsletter-modals';
import { SocialButtons } from './social-buttons';

export function DesktopFooter() {
return (
<footer className='hidden md:flex p-10 justify-between items-center gap-10 flex-row'>
<div className='flex items-center gap-5 text-[40px]'>
<Link href='https://www.instagram.com/simonyikonf' className='brand-link' target='blank'>
<FaInstagram />
</Link>
<Link href='https://www.facebook.com/events/1060756212046229' className='brand-link' target='blank'>
<FaFacebook />
</Link>
<Link
href='https://www.youtube.com/watch?v=QDKDaMKqcoQ&list=PLovp3RCdzQGx_lKpvCgUJT6n-wJazXKrL'
className='brand-link'
target='blank'
>
<FaYoutube />
</Link>
<SocialButtons />
<div className='absolute left-1/2 -translate-x-1/2'>
<NewsletterModals />
</div>
<div className='flex items-center gap-10'>
<Link href='https://schdesign.hu' className='brand-link' target='blank'>
Expand Down
22 changes: 22 additions & 0 deletions src/components/footer/social-buttons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Link from 'next/link';
import { FaFacebook, FaInstagram, FaYoutube } from 'react-icons/fa';

export function SocialButtons() {
return (
<div className='flex items-center gap-5 text-[40px]'>
<Link href='https://www.instagram.com/simonyikonf' className='brand-link' target='blank'>
<FaInstagram />
</Link>
<Link href='https://www.facebook.com/events/1060756212046229' className='brand-link' target='blank'>
<FaFacebook />
</Link>
<Link
href='https://www.youtube.com/watch?v=QDKDaMKqcoQ&list=PLovp3RCdzQGx_lKpvCgUJT6n-wJazXKrL'
className='brand-link'
target='blank'
>
<FaYoutube />
</Link>
</div>
);
}
2 changes: 1 addition & 1 deletion src/components/navbar/desktop-navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { UniLogos } from '@/components/uni-logos';

export function DesktopNavbar() {
return (
<nav className='hidden md:flex p-10 md:p-15 justify-between items-center flex-wrap gap-10 flex-col md:flex-row'>
<nav className='hidden md:flex p-10 md:p-15 pb-0 justify-between items-center flex-wrap gap-10 flex-col md:flex-row'>
<UniLogos />
<div className='flex items-center gap-10'>
<NavbarItems />
Expand Down
3 changes: 3 additions & 0 deletions src/components/navbar/navbar-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import Link from 'next/link';
export function NavbarItems() {
return (
<>
<Link href='/' className='hover:text-brand text-xl'>
főoldal
</Link>
<Link href='/conferences' className='hover:text-brand text-xl'>
konferenciák
</Link>
Expand Down
12 changes: 9 additions & 3 deletions src/components/navbar/navbar-mobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@

import clsx from 'clsx';
import Image from 'next/image';
import { useState } from 'react';
import Link from 'next/link';
import { MouseEventHandler, useState } from 'react';
import { TbMenu2 } from 'react-icons/tb';

import { NavbarItems } from '@/components/navbar/navbar-items';

export function NavbarMobile() {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
const onLinkClick: MouseEventHandler = (e) => {
if ((e.target as HTMLElement).tagName === 'A') setIsOpen(false);
};
return (
<nav className='w-full p-5 md:hidden'>
<nav className='w-full p-5 md:hidden sticky top-0 z-20' onClick={onLinkClick}>
<div className='w-full p-5 box-border rounded-md bg-white bg-opacity-10 backdrop-blur-sm'>
<div className='w-full flex items-center justify-between gap-5'>
<Image src='/img/icon-base.svg' alt='Simonyi Konferencia' width={40} height={40} />
<Link href='/' onClick={() => setIsOpen(false)}>
<Image src='/img/icon-base.svg' alt='Simonyi Konferencia' width={40} height={40} />
</Link>
<button onClick={toggle}>
<TbMenu2 size={40} />
</button>
Expand Down
118 changes: 118 additions & 0 deletions src/components/newsletter/newsletter-modals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use client';
import { Dialog } from '@headlessui/react';
import * as EmailValidator from 'email-validator';
import { useState } from 'react';
import { FaCheckCircle, FaTimes } from 'react-icons/fa';

import { addToGroup } from '@/app/actions';

import { WhiteButton } from '../white-button';

export function NewsletterModals() {
const [isSubscribeOpen, setIsSubscribeOpen] = useState(false);
const [isSuccessOpen, setIsSuccessOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [email, setEmail] = useState('');
const [accepted, setAccepted] = useState(false);
const [error, setError] = useState('');

const onSubscribe = async () => {
if (accepted && email) {
setIsLoading(true);
const status = await addToGroup({ email });
setIsLoading(false);
switch (status) {
case 200:
onSubscribeClose();
setIsSuccessOpen(true);
break;
case 400:
setError('Érvénytelen email cím!');
break;
case 409:
setError('Ez az email cím már fel van iratkozva a hírlevélre!');
break;
default:
setError('Ismeretlen hiba!');
}
}
};

const onSubscribeClose = () => {
setEmail('');
setError('');
setAccepted(false);
setIsSubscribeOpen(false);
};

return (
<>
<WhiteButton onClick={() => setIsSubscribeOpen(true)}>Feliratkozás a hírlevélre</WhiteButton>
<Dialog open={isSubscribeOpen} onClose={onSubscribeClose} className='relative z-50'>
<div className='fixed inset-0 bg-black/80' aria-hidden='true' />

<div className='fixed inset-0 flex w-screen items-center justify-center p-4'>
<Dialog.Panel className='mx-auto max-w-lg rounded bg-[#0f181c] p-8'>
<div className='flex justify-between items-center mb-5'>
<Dialog.Title className='font-bold text-2xl '>Hírlevél</Dialog.Title>
<div className='hover:text-gray-400 hover:cursor-pointer text-xl' onClick={onSubscribeClose}>
<FaTimes />
</div>
</div>

<div className='flex flex-col gap-2'>
<p className='mb-2 text-justify'>
Ha szeretnél értesülni a legfontosabb hírekről, mint például előadások, nyereményjáték, iratkozz fel a
hírlevelünkre!
</p>
<div>
<input
className='mr-2'
type='checkbox'
id='accept'
checked={accepted}
onChange={(e) => setAccepted(e.target.checked)}
/>
<label htmlFor='accept'>
Beleegyezem, hogy a konferenciáig havonta maximum két emailt fogok kapni az alábbi email címre.
</label>
</div>

<input
placeholder='[email protected]'
className='text-black p-2 rounded-md mb-2'
value={email}
onChange={(e) => {
setEmail(e.target.value);
setError('');
}}
type='email'
/>
{error && <p className='text-red-500'>{error}</p>}
<WhiteButton
disabled={!EmailValidator.validate(email) || !accepted || !!error || isLoading}
onClick={onSubscribe}
>
{isLoading ? 'Kérjük várj...' : 'Feliratkozás'}
</WhiteButton>
</div>
</Dialog.Panel>
</div>
</Dialog>
<Dialog open={isSuccessOpen} onClose={() => setIsSuccessOpen(false)} className='relative z-50'>
<div className='fixed inset-0 bg-black/80' aria-hidden='true' />

<div className='fixed inset-0 flex w-screen items-center justify-center p-4'>
<Dialog.Panel className='mx-auto max-w-lg rounded bg-[#0f181c] p-8 flex flex-col items-center gap-5'>
<div className='text-8xl text-white'>
<FaCheckCircle />
</div>
<Dialog.Title className='font-bold text-2xl mb-5'>Sikeresen feliratkoztál a hírlevélre!</Dialog.Title>

<WhiteButton onClick={() => setIsSuccessOpen(false)}>Rendben</WhiteButton>
</Dialog.Panel>
</div>
</Dialog>
</>
);
}
18 changes: 18 additions & 0 deletions src/components/white-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { MouseEventHandler, PropsWithChildren } from 'react';

type Props = {
onClick: MouseEventHandler<HTMLButtonElement>;
disabled?: boolean;
} & PropsWithChildren;

export function WhiteButton({ onClick, children, disabled = false }: Props) {
return (
<button
onClick={onClick}
disabled={disabled}
className='bg-white text-xl rounded-lg font-bold py-2 px-4 text-black hover:bg-gray-300 disabled:bg-gray-500 disabled:cursor-not-allowed'
>
{children}
</button>
);
}
Loading

0 comments on commit 230d17d

Please sign in to comment.