Review adjustments (#11)
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
## Next up
|
||||
|
||||
- Adjust card size
|
||||
- Batch email (Resend: ETA early 2024)
|
||||
- Custom url shortener for links in the newsletter
|
||||
- Cron every 10 minutes: people are more likely to open the newsletter if delivered around the time when they subscribed (if cron becomes not enough, then the cost of sending all the emails might be a bigger issue)
|
||||
|
||||
@@ -77,3 +77,80 @@
|
||||
.styledH4 {
|
||||
@apply text-base font-medium;
|
||||
}
|
||||
|
||||
/* Card border gradient */
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.gradient-border {
|
||||
--radius: 5px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
background: #222;
|
||||
border-radius: var(--radius);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: calc(-1 * var(--radius));
|
||||
left: calc(-1 * var(--radius));
|
||||
z-index: -1;
|
||||
width: calc(100% + var(--radius) * 2);
|
||||
height: calc(100% + var(--radius) * 2);
|
||||
background: linear-gradient(
|
||||
60deg,
|
||||
hsl(224, 85%, 66%),
|
||||
hsl(269, 85%, 66%),
|
||||
hsl(314, 85%, 66%),
|
||||
hsl(359, 85%, 66%),
|
||||
hsl(44, 85%, 66%),
|
||||
hsl(89, 85%, 66%),
|
||||
hsl(134, 85%, 66%),
|
||||
hsl(179, 85%, 66%)
|
||||
);
|
||||
background-size: 300% 300%;
|
||||
background-position: 0 50%;
|
||||
border-radius: calc(2 * var(--radius));
|
||||
animation: moveGradient 4s alternate infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes moveGradient {
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Button gradient */
|
||||
.btn-grad {
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
#da22ff 0%,
|
||||
#9733ee 51%,
|
||||
#da22ff 100%
|
||||
);
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
transition: 0.5s;
|
||||
background-size: 200% auto;
|
||||
color: white;
|
||||
box-shadow: 0 0 20px #eee;
|
||||
border-radius: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.btn-grad:hover {
|
||||
background-position: right center;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@ export default function RootLayout({
|
||||
fontSans.variable
|
||||
)}
|
||||
>
|
||||
<Background>{children}</Background>
|
||||
<Background>
|
||||
<div style={{ zIndex: 2 }}>{children}</div>
|
||||
</Background>
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
24
app/page.tsx
24
app/page.tsx
@@ -80,21 +80,27 @@ export default function Home() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-12 align-top'>
|
||||
<div className='mx-2 h-44'>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className='flex space-x-4'
|
||||
className='flex flex-col space-y-4'
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='email'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className='h-4'>
|
||||
<FormMessage className='text-center' />
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input placeholder='example@example.com' {...field} />
|
||||
<Input
|
||||
placeholder='example@example.com'
|
||||
className='text-center'
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@@ -103,15 +109,19 @@ export default function Home() {
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<p className='py-1 text-center text-xs text-gray-600'>
|
||||
You can rest assured that we will fill your inbox with spam. We
|
||||
don't like it either! 🙂
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
style='text-center'
|
||||
title='Hackernews + newsletter'
|
||||
description='Top stories from Hackernews. Once a day. Every day.'
|
||||
style='text-center max-w-96'
|
||||
title='Interested in keeping up with the latest from the tech world? 👩💻'
|
||||
description='Subscribe to our newsletter! The top stories from Hackernews for you. Once a day. Every day.'
|
||||
content={render()}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Card } from '../../components/custom/card';
|
||||
|
||||
export default function Privacy() {
|
||||
const body = (
|
||||
<div>
|
||||
<div className='my-2 max-h-[60vh] overflow-auto'>
|
||||
<p>
|
||||
This Privacy Policy describes Our policies and procedures on the
|
||||
collection, use and disclosure of Your information when You use the
|
||||
@@ -430,10 +430,10 @@ export default function Privacy() {
|
||||
|
||||
return (
|
||||
<Card
|
||||
style='max-h-[90vh] max-w-[90vw]'
|
||||
title='Privacy Policy'
|
||||
description='Last updated: December 03, 2023'
|
||||
content={body}
|
||||
style='w-2/3 h-[90vh]'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,21 +80,23 @@ export default function Unsubscribe() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-12 align-top'>
|
||||
<div className='mb-5 h-32'>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className='flex space-x-4'
|
||||
className='flex flex-col space-y-4'
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='email'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className='h-4'>
|
||||
<FormMessage className='text-center' />
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input placeholder='example@example.com' {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@@ -109,7 +111,7 @@ export default function Unsubscribe() {
|
||||
|
||||
return (
|
||||
<Card
|
||||
style='text-center'
|
||||
style='text-center max-w-80'
|
||||
title='Unsubscribe'
|
||||
description='You sure you want to leave? :('
|
||||
content={render()}
|
||||
|
||||
@@ -36,45 +36,34 @@ export const Card = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
console.log(isMobile);
|
||||
return (
|
||||
<CardUI className={`max-h-[90vh] w-[90%] p-4`}>
|
||||
<CardHeader className='text-center'>
|
||||
<CardTitle>{title}</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='max-h-[60vh] overflow-auto'>
|
||||
{content}
|
||||
</CardContent>
|
||||
{footer && (
|
||||
<CardFooter className='flex justify-center p-4'>
|
||||
<Footer />
|
||||
</CardFooter>
|
||||
)}
|
||||
</CardUI>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='gradient-border'>
|
||||
<CardUI
|
||||
style={{
|
||||
boxShadow: '0 16px 32px 0 rgba(0, 0, 0, 0.6)'
|
||||
}}
|
||||
className={`${style ?? 'sm:w-2/3 md:w-2/5 lg:w-1/3 xl:w-1/4'} p-4`}
|
||||
className={`max-h-[90vh] w-[90vw] p-8 ${style}`}
|
||||
>
|
||||
<CardHeader className='text-center'>
|
||||
<CardHeader>
|
||||
<p className='text-xs uppercase text-gray-500'>
|
||||
Hackernews + newsletter
|
||||
</p>
|
||||
<CardTitle>{title}</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='h-[80%] flex-grow overflow-auto'>
|
||||
{isMobile ? (
|
||||
<CardContent>{content}</CardContent>
|
||||
) : (
|
||||
<CardContent className='flex max-h-[60vh] flex-grow justify-center overflow-auto'>
|
||||
{content}
|
||||
</CardContent>
|
||||
)}
|
||||
{footer && (
|
||||
<CardFooter className=' flex justify-center p-4'>
|
||||
<CardFooter>
|
||||
<Footer />
|
||||
</CardFooter>
|
||||
)}
|
||||
</CardUI>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,55 +1,19 @@
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import * as React from 'react';
|
||||
import { cn } from '../../utils/ui';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'bg-primary text-primary-foreground hover:bg-primary/90 bg-blue-500 hover:bg-blue-700',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline:
|
||||
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline'
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export type ButtonProps = {
|
||||
asChild?: boolean;
|
||||
} & React.ButtonHTMLAttributes<HTMLButtonElement> &
|
||||
VariantProps<typeof buttonVariants>;
|
||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
({ asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
<Comp className={cn('btn-grad', 'btn-grad-hover')} ref={ref} {...props} />
|
||||
);
|
||||
}
|
||||
);
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export { Button, buttonVariants };
|
||||
export { Button };
|
||||
|
||||
@@ -22,7 +22,7 @@ const CardHeader = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
||||
className={cn('flex flex-col space-y-1.5 text-center', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
@@ -59,7 +59,7 @@ const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('px-8 py-4', className)} {...props} />
|
||||
<div ref={ref} className={cn('py-4', className)} {...props} />
|
||||
));
|
||||
CardContent.displayName = 'CardContent';
|
||||
|
||||
@@ -69,7 +69,7 @@ const CardFooter = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex items-center p-8', className)}
|
||||
className={cn('flex items-center justify-center', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"next": "^14.1.0",
|
||||
"postcss-nesting": "^12.0.2",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.48.2",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': 'postcss-nesting',
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
|
||||
15
yarn.lock
15
yarn.lock
@@ -198,6 +198,11 @@
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
|
||||
"@csstools/selector-specificity@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz#d84597fbc0f897240c12fc0a31e492b036c70e40"
|
||||
integrity sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||
@@ -3189,7 +3194,15 @@ postcss-nested@^6.0.1:
|
||||
dependencies:
|
||||
postcss-selector-parser "^6.0.11"
|
||||
|
||||
postcss-selector-parser@^6.0.11:
|
||||
postcss-nesting@^12.0.2:
|
||||
version "12.0.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-12.0.2.tgz#cb92061347db3e7c38c174c97cb01feff2eef439"
|
||||
integrity sha512-63PpJHSeNs93S3ZUIyi+7kKx4JqOIEJ6QYtG3x+0qA4J03+4n0iwsyA1GAHyWxsHYljQS4/4ZK1o2sMi70b5wQ==
|
||||
dependencies:
|
||||
"@csstools/selector-specificity" "^3.0.1"
|
||||
postcss-selector-parser "^6.0.13"
|
||||
|
||||
postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13:
|
||||
version "6.0.15"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535"
|
||||
integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==
|
||||
|
||||
Reference in New Issue
Block a user