style: linting and formatting
This commit is contained in:
@@ -1,3 +1,22 @@
|
|||||||
{
|
{
|
||||||
"extends": "next/core-web-vitals"
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"next/core-web-vitals",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": ["error", "type"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
.husky/commit-msg
Executable file
4
.husky/commit-msg
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no-install commitlint --edit $1
|
||||||
7
.husky/pre-commit
Executable file
7
.husky/pre-commit
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
yarn audit
|
||||||
|
yarn format
|
||||||
|
yarn lint
|
||||||
|
yarn typecheck
|
||||||
10
.prettierrc
Normal file
10
.prettierrc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"jsxSingleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
||||||
@@ -14,22 +14,22 @@ export async function GET(request: Request) {
|
|||||||
const response = await hackernewsApi();
|
const response = await hackernewsApi();
|
||||||
|
|
||||||
return new NextResponse(JSON.stringify(response), {
|
return new NextResponse(JSON.stringify(response), {
|
||||||
status: 200,
|
status: 200
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function hackernewsApi() {
|
async function hackernewsApi() {
|
||||||
const topstories: number[] = await fetch(topNews).then((res) => res.json());
|
const topstories: number[] = await fetch(topNews).then(res => res.json());
|
||||||
|
|
||||||
console.log('topstories', topstories);
|
console.log('topstories', topstories);
|
||||||
|
|
||||||
const newsPromises = topstories
|
const newsPromises = topstories
|
||||||
.splice(0, Number(process.env.NEWS_LIMIT))
|
.splice(0, Number(process.env.NEWS_LIMIT))
|
||||||
.map(async (id) => {
|
.map(async id => {
|
||||||
console.log('id', id);
|
console.log('id', id);
|
||||||
const sourceNews: z.infer<typeof NewsSchema> = await fetch(
|
const sourceNews: z.infer<typeof NewsSchema> = await fetch(
|
||||||
singleNews(id)
|
singleNews(id)
|
||||||
).then((res) => res.json());
|
).then(res => res.json());
|
||||||
|
|
||||||
console.log('sourceNews', sourceNews);
|
console.log('sourceNews', sourceNews);
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ async function hackernewsApi() {
|
|||||||
by: sourceNews.by,
|
by: sourceNews.by,
|
||||||
time: sourceNews.time,
|
time: sourceNews.time,
|
||||||
url: sourceNews.url,
|
url: sourceNews.url,
|
||||||
score: sourceNews.score,
|
score: sourceNews.score
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
title: sourceNews.title,
|
title: sourceNews.title,
|
||||||
@@ -51,18 +51,18 @@ async function hackernewsApi() {
|
|||||||
by: sourceNews.by,
|
by: sourceNews.by,
|
||||||
time: sourceNews.time,
|
time: sourceNews.time,
|
||||||
url: sourceNews.url,
|
url: sourceNews.url,
|
||||||
score: sourceNews.score,
|
score: sourceNews.score
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
id,
|
id
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const newsIds = await Promise.all(newsPromises);
|
const newsIds = await Promise.all(newsPromises);
|
||||||
|
|
||||||
return newsIds.map((news) => news.id);
|
return newsIds.map(news => news.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { fromZodError } from 'zod-validation-error';
|
||||||
import prisma from '../../../prisma/prisma';
|
import prisma from '../../../prisma/prisma';
|
||||||
import { ResponseSchema, SubscribeFormSchema } from '../../utils/types';
|
import { ResponseSchema, SubscribeFormSchema } from '../../utils/types';
|
||||||
|
|
||||||
@@ -7,7 +8,8 @@ export async function POST(request: Request) {
|
|||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const validation = SubscribeFormSchema.safeParse(body);
|
const validation = SubscribeFormSchema.safeParse(body);
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
return new Response('Bad request', { status: 400 });
|
const message = fromZodError(validation.error);
|
||||||
|
return new Response(message.message, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { email, targetingAllowed } = validation.data;
|
const { email, targetingAllowed } = validation.data;
|
||||||
@@ -15,18 +17,18 @@ export async function POST(request: Request) {
|
|||||||
await prisma.user.upsert({
|
await prisma.user.upsert({
|
||||||
create: {
|
create: {
|
||||||
email,
|
email,
|
||||||
targetingAllowed,
|
targetingAllowed
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
targetingAllowed,
|
targetingAllowed
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
email,
|
email
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const message: z.infer<typeof ResponseSchema> = {
|
const message: z.infer<typeof ResponseSchema> = {
|
||||||
message: `${email} subscribed!`,
|
message: `${email} subscribed!`
|
||||||
};
|
};
|
||||||
return new Response(JSON.stringify(message), { status: 200 });
|
return new Response(JSON.stringify(message), { status: 200 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { fromZodError } from 'zod-validation-error';
|
||||||
import prisma from '../../../prisma/prisma';
|
import prisma from '../../../prisma/prisma';
|
||||||
import { ResponseSchema, UnsubscribeFormSchema } from '../../utils/types';
|
import { ResponseSchema, UnsubscribeFormSchema } from '../../utils/types';
|
||||||
|
|
||||||
@@ -7,7 +8,8 @@ export async function POST(request: Request) {
|
|||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const validation = UnsubscribeFormSchema.safeParse(body);
|
const validation = UnsubscribeFormSchema.safeParse(body);
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
return new Response('Bad request', { status: 400 });
|
const message = fromZodError(validation.error);
|
||||||
|
return new Response(message.message, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { email } = validation.data;
|
const { email } = validation.data;
|
||||||
@@ -15,15 +17,15 @@ export async function POST(request: Request) {
|
|||||||
try {
|
try {
|
||||||
await prisma.user.delete({
|
await prisma.user.delete({
|
||||||
where: {
|
where: {
|
||||||
email,
|
email
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const message: z.infer<typeof ResponseSchema> = {
|
const message: z.infer<typeof ResponseSchema> = {
|
||||||
message: `${email} unsubscribe!`,
|
message: `${email} unsubscribe!`
|
||||||
};
|
};
|
||||||
return new Response(JSON.stringify(message), { status: 200 });
|
return new Response(JSON.stringify(message), { status: 200 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,18 @@ html,
|
|||||||
body {
|
body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
font-family:
|
||||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
Segoe UI,
|
||||||
|
Roboto,
|
||||||
|
Oxygen,
|
||||||
|
Ubuntu,
|
||||||
|
Cantarell,
|
||||||
|
Fira Sans,
|
||||||
|
Droid Sans,
|
||||||
|
Helvetica Neue,
|
||||||
|
sans-serif;
|
||||||
background: #1e1e1e;
|
background: #1e1e1e;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -28,8 +38,11 @@ body {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
width: 85%;
|
width: 85%;
|
||||||
padding: 50px;
|
padding: 50px;
|
||||||
box-shadow: 0 54px 55px rgb(78 78 78 / 25%), 0 -12px 30px rgb(78 78 78 / 25%),
|
box-shadow:
|
||||||
0 4px 6px rgb(78 78 78 / 25%), 0 12px 13px rgb(78 78 78 / 25%),
|
0 54px 55px rgb(78 78 78 / 25%),
|
||||||
|
0 -12px 30px rgb(78 78 78 / 25%),
|
||||||
|
0 4px 6px rgb(78 78 78 / 25%),
|
||||||
|
0 12px 13px rgb(78 78 78 / 25%),
|
||||||
0 -3px 5px rgb(78 78 78 / 25%);
|
0 -3px 5px rgb(78 78 78 / 25%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import type { Metadata } from 'next'
|
import type { Metadata } from 'next';
|
||||||
import { Inter } from 'next/font/google'
|
import { Inter } from 'next/font/google';
|
||||||
import './globals.css'
|
import './globals.css';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Create Next App',
|
title: 'Create Next App',
|
||||||
description: 'Generated by create next app',
|
description: 'Generated by create next app'
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang='en'>
|
||||||
<body className={inter.className}>{children}</body>
|
<body className={inter.className}>{children}</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<VerticalLayout>
|
<VerticalLayout>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
<Button label="Subscribe" onClick={() => router.push('/subscribe')} />
|
<Button label='Subscribe' onClick={() => router.push('/subscribe')} />
|
||||||
<Button label="Unsubscribe" onClick={() => router.push('/unsubscribe')} />
|
<Button label='Unsubscribe' onClick={() => router.push('/unsubscribe')} />
|
||||||
</VerticalLayout>
|
</VerticalLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ export default function Home() {
|
|||||||
const response = await fetch('/api/subscribe', {
|
const response = await fetch('/api/subscribe', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: data.get('email'),
|
email: data.get('email'),
|
||||||
targetingAllowed: isChecked,
|
targetingAllowed: isChecked
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response?.ok) {
|
if (!response?.ok) {
|
||||||
@@ -33,6 +33,8 @@ export default function Home() {
|
|||||||
const formResponse: z.infer<typeof ResponseSchema> =
|
const formResponse: z.infer<typeof ResponseSchema> =
|
||||||
await response.json();
|
await response.json();
|
||||||
|
|
||||||
|
console.log(formResponse);
|
||||||
|
|
||||||
router.push('/success');
|
router.push('/success');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -46,34 +48,34 @@ export default function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<VerticalLayout>
|
<VerticalLayout>
|
||||||
<form className="container" onSubmit={handleSubmit}>
|
<form className='container' onSubmit={handleSubmit}>
|
||||||
<h1>Subscribe to newsletter</h1>
|
<h1>Subscribe to newsletter</h1>
|
||||||
<div className="email block">
|
<div className='email block'>
|
||||||
<label htmlFor="frm-email">Email</label>
|
<label htmlFor='frm-email'>Email</label>
|
||||||
<input
|
<input
|
||||||
placeholder="example@email.com"
|
placeholder='example@email.com'
|
||||||
id="email"
|
id='email'
|
||||||
type="email"
|
type='email'
|
||||||
name="email"
|
name='email'
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="checkbox block">
|
<div className='checkbox block'>
|
||||||
<label htmlFor="frm-checkbox">Allow advertising</label>
|
<label htmlFor='frm-checkbox'>Allow advertising</label>
|
||||||
<input
|
<input
|
||||||
id="targetingAllowed"
|
id='targetingAllowed'
|
||||||
type="checkbox"
|
type='checkbox'
|
||||||
name="targetingAllowed"
|
name='targetingAllowed'
|
||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onChange={handleCheckboxChange}
|
onChange={handleCheckboxChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="button block">
|
<div className='button block'>
|
||||||
<button type="submit">Subscribe</button>
|
<button type='submit'>Subscribe</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<Button label="Home" onClick={() => router.push('/')} />
|
<Button label='Home' onClick={() => router.push('/')} />
|
||||||
<Button label="Unsubscribe" onClick={() => router.push('/unsubscribe')} />
|
<Button label='Unsubscribe' onClick={() => router.push('/unsubscribe')} />
|
||||||
</VerticalLayout>
|
</VerticalLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useRouter } from 'next/router';
|
'use client';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { Button } from '../../components/Button';
|
import { Button } from '../../components/Button';
|
||||||
import { VerticalLayout } from '../../components/VerticalLayout';
|
import { VerticalLayout } from '../../components/VerticalLayout';
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<VerticalLayout>
|
<VerticalLayout>
|
||||||
<h1>Success!</h1>
|
<h1>Success!</h1>
|
||||||
<Button label="Home" onClick={() => router.push('/')} />
|
<Button label='Home' onClick={() => router.push('/')} />
|
||||||
</VerticalLayout>
|
</VerticalLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ export default function Home() {
|
|||||||
const response = await fetch('/api/unsubscribe', {
|
const response = await fetch('/api/unsubscribe', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: data.get('email'),
|
email: data.get('email')
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response?.ok) {
|
if (!response?.ok) {
|
||||||
@@ -31,6 +31,8 @@ export default function Home() {
|
|||||||
const formResponse: z.infer<typeof ResponseSchema> =
|
const formResponse: z.infer<typeof ResponseSchema> =
|
||||||
await response.json();
|
await response.json();
|
||||||
|
|
||||||
|
console.log(formResponse);
|
||||||
|
|
||||||
router.push('/success');
|
router.push('/success');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -40,24 +42,24 @@ export default function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<VerticalLayout>
|
<VerticalLayout>
|
||||||
<form className="container" onSubmit={handleSubmit}>
|
<form className='container' onSubmit={handleSubmit}>
|
||||||
<h1>Unsubscribe newsletter</h1>
|
<h1>Unsubscribe newsletter</h1>
|
||||||
<div className="email block">
|
<div className='email block'>
|
||||||
<label htmlFor="frm-email">Email</label>
|
<label htmlFor='frm-email'>Email</label>
|
||||||
<input
|
<input
|
||||||
placeholder="example@email.com"
|
placeholder='example@email.com'
|
||||||
id="email"
|
id='email'
|
||||||
type="email"
|
type='email'
|
||||||
name="email"
|
name='email'
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="button block">
|
<div className='button block'>
|
||||||
<button type="submit">Unsubscribe</button>
|
<button type='submit'>Unsubscribe</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<Button label="Home" onClick={() => router.push('/')} />
|
<Button label='Home' onClick={() => router.push('/')} />
|
||||||
<Button label="Subscribe" onClick={() => router.push('/subscribe')} />
|
<Button label='Subscribe' onClick={() => router.push('/subscribe')} />
|
||||||
</VerticalLayout>
|
</VerticalLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const ResponseSchema = z.object({
|
export const ResponseSchema = z.object({
|
||||||
message: z.string(),
|
message: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SubscribeFormSchema = z.object({
|
export const SubscribeFormSchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
targetingAllowed: z.boolean(),
|
targetingAllowed: z.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UnsubscribeFormSchema = z.object({
|
export const UnsubscribeFormSchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NewsSchema = z.object({
|
export const NewsSchema = z.object({
|
||||||
@@ -21,5 +21,5 @@ export const NewsSchema = z.object({
|
|||||||
by: z.string(),
|
by: z.string(),
|
||||||
time: z.number(),
|
time: z.number(),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
score: z.number(),
|
score: z.number()
|
||||||
});
|
});
|
||||||
|
|||||||
1
commitlint.config.ts
Normal file
1
commitlint.config.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = { extends: ['@commitlint/config-conventional'] };
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
interface ButtonProps {
|
type ButtonProps = {
|
||||||
label: string;
|
label: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Button = ({ label, onClick }: ButtonProps) => (
|
export const Button = ({ label, onClick }: ButtonProps) => (
|
||||||
<button onClick={onClick} key={1} className="overflow-hidden rounded-md">
|
<button onClick={onClick} key={1} className="overflow-hidden rounded-md">
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ export const VerticalLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
gap: '16px',
|
gap: "16px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {}
|
const nextConfig = {};
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig;
|
||||||
|
|||||||
25
package.json
25
package.json
@@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "next-newsletter",
|
"name": "next-newsletter",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"description": "Template for NodeJS APIs with TypeScript, with configurations for linting and testing",
|
||||||
|
"author": "riccardo.s@hey.com",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "prisma generate && next build",
|
"build": "prisma generate && next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
"format": "prettier --config .prettierrc 'app/' --write",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
"prepare": "husky install",
|
||||||
"vercel:link": "vercel link",
|
"vercel:link": "vercel link",
|
||||||
"vercel:env": "vercel env pull .env",
|
"vercel:env": "vercel env pull .env",
|
||||||
"prisma:push": "npx prisma db push",
|
"prisma:push": "npx prisma db push",
|
||||||
@@ -15,21 +18,39 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^5.6.0",
|
"@prisma/client": "^5.6.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4",
|
||||||
|
"zod-validation-error": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^18.4.3",
|
||||||
|
"@commitlint/config-conventional": "^18.4.3",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@typescript-eslint/parser": "^6.12.0",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.0.3",
|
"eslint-config-next": "14.0.3",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"husky": "^8.0.3",
|
||||||
|
"lint-staged": "^15.1.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
|
"prettier": "^3.1.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.7",
|
||||||
"prisma": "^5.6.0",
|
"prisma": "^5.6.0",
|
||||||
"tailwindcss": "^3.3.0",
|
"tailwindcss": "^3.3.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.ts": [
|
||||||
|
"eslint --quiet --fix"
|
||||||
|
],
|
||||||
|
"*.{json,ts}": [
|
||||||
|
"prettier --write --ignore-unknown"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ module.exports = {
|
|||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
// PrismaClient is attached to the `global` object in development to prevent
|
// PrismaClient is attached to the `global` object in development to prevent
|
||||||
// exhausting your database connection limit.
|
// exhausting your database connection limit.
|
||||||
@@ -10,6 +10,6 @@ const globalForPrisma = global as unknown as { prisma: PrismaClient };
|
|||||||
|
|
||||||
export const prisma = globalForPrisma.prisma || new PrismaClient();
|
export const prisma = globalForPrisma.prisma || new PrismaClient();
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||||
|
|
||||||
export default prisma;
|
export default prisma;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import type { Config } from 'tailwindcss'
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: [
|
content: [
|
||||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||||
'gradient-conic':
|
"gradient-conic":
|
||||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
};
|
||||||
export default config
|
export default config;
|
||||||
|
|||||||
Reference in New Issue
Block a user