Skip to content

Commit e5eb23f

Browse files
initial commit
0 parents  commit e5eb23f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+10133
-0
lines changed

.env.example

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
MONGODB_URI=
2+
REDIS_URI=
3+
4+
ACCESS_TOKEN_PRIVATE_KEY=
5+
ACCESS_TOKEN_PUBLIC_KEY=
6+
7+
REFRESH_TOKEN_PRIVATE_KEY=
8+
REFRESH_TOKEN_PUBLIC_KEY=

.eslintrc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

.gitignore

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# NextJS With GraphQL
2+
3+
Blog Application using NextJS with GraphQL
4+
5+
---
6+
7+
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
8+
9+
## Getting Started
10+
11+
First, run the development server:
12+
13+
```bash
14+
npm run dev
15+
# or
16+
yarn dev
17+
# or
18+
pnpm dev
19+
```
20+
21+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
22+
23+
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
24+
25+
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
26+
27+
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
28+
29+
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
30+
31+
## Learn More
32+
33+
To learn more about Next.js, take a look at the following resources:
34+
35+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
36+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
37+
38+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
39+
40+
## Deploy on Vercel
41+
42+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
43+
44+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
45+
46+
## Acknowledgment
47+
48+
This project was built with the help of [nextjs-typegraphql repo](https://github.com/wpcodevo/nextjs-typegraphql-api)

client/components/FileUpload.tsx

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React, { useCallback } from "react";
2+
import { Controller, useController, useFormContext } from "react-hook-form";
3+
import useStore from "../store";
4+
import Spinner from "./Spinner";
5+
6+
const UPLOAD_PRESET = "nextjs-jwt";
7+
const CLOUDINARY_URL = "https://api.cloudinary.com/v1_1/dxq7xwg2w/image/upload";
8+
9+
interface IFileUpload {
10+
name: string;
11+
}
12+
13+
const FileUpload: React.FC<IFileUpload> = ({ name }) => {
14+
const {
15+
control,
16+
formState: { errors },
17+
} = useFormContext();
18+
const { field } = useController({ name, control });
19+
const store = useStore();
20+
21+
const onFileDrop = useCallback(
22+
async (e: React.SyntheticEvent<EventTarget>) => {
23+
const target = e.target as HTMLInputElement;
24+
if (!target.files) return;
25+
const newFile = Object.values(target.files).map((file: File) => file);
26+
27+
const formData = new FormData();
28+
formData.append("file", newFile[0]);
29+
formData.append("upload_preset", UPLOAD_PRESET);
30+
31+
store.setUploadImage(true);
32+
try {
33+
const response = await fetch(CLOUDINARY_URL, {
34+
method: "post",
35+
body: formData,
36+
});
37+
const data = await response.json();
38+
39+
if (data.secure_url) {
40+
field.onChange(data.secure_url);
41+
}
42+
return data;
43+
} catch (err) {
44+
console.error("error in upload image: ", err);
45+
} finally {
46+
store.setUploadImage(false);
47+
}
48+
},
49+
[field, store]
50+
);
51+
return (
52+
<Controller
53+
name={name}
54+
defaultValue=""
55+
control={control}
56+
render={({ field: { name, onBlur, ref } }) => (
57+
<>
58+
<div className="mb-2 flex justify-between items-center">
59+
<div>
60+
<span className="block mb-2">Choose profile photo</span>
61+
<input
62+
className="block text-sm mb-2 text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-violet-50 file:text-violet-700 hover:file:bg-violet-100"
63+
type="file"
64+
name={name}
65+
onBlur={onBlur}
66+
ref={ref}
67+
onChange={onFileDrop}
68+
multiple={false}
69+
accept="image/jpg, image/png, image/jpeg"
70+
/>
71+
</div>
72+
<div>
73+
{store.uploadingImage && <Spinner color="text-yellow-400" />}
74+
</div>
75+
</div>
76+
<p
77+
className={`text-red-500 text-xs italic mb-2 ${
78+
errors[name] ? "visible" : "invisible"
79+
}`}
80+
>
81+
{errors[name] && (errors[name]?.message as string)}
82+
</p>
83+
</>
84+
)}
85+
/>
86+
);
87+
};
88+
89+
export default FileUpload;

client/components/FormInput.tsx

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from "react";
2+
import { useFormContext } from "react-hook-form";
3+
4+
interface FormInputProps {
5+
label: string;
6+
name: string;
7+
type?: string;
8+
}
9+
10+
const FormInput: React.FC<FormInputProps> = ({
11+
label,
12+
name,
13+
type = "text",
14+
}) => {
15+
const {
16+
register,
17+
formState: { errors },
18+
} = useFormContext();
19+
return (
20+
<div>
21+
<label htmlFor={name} className="block text-ct-blue-600 mb-3">
22+
{label}
23+
</label>
24+
<input
25+
type={type}
26+
placeholder=" "
27+
className="block w-full rounded-2xl appearance-none focus:outline-none py-2 px-4"
28+
{...register(name)}
29+
/>
30+
{errors[name] && (
31+
<span className="text-red-500 text-xs pt-1 block">
32+
{errors[name]?.message as string}
33+
</span>
34+
)}
35+
</div>
36+
);
37+
};
38+
39+
export default FormInput;
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Spinner from "./Spinner";
2+
3+
const FullScreenLoader = () => {
4+
return (
5+
<div className="w-screen h-screen fixed">
6+
<div className="absolute top-64 left-1/2 -translate-x-1/2">
7+
<Spinner width={8} height={8} />
8+
</div>
9+
</div>
10+
);
11+
};
12+
13+
export default FullScreenLoader;

client/components/Header.tsx

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import Link from "next/link";
2+
import { toast } from "react-toastify";
3+
import { useRouter } from "next/router";
4+
import { useLogoutUserQuery } from "../generated/graphql";
5+
import graphqlRequestClient, {
6+
queryClient,
7+
} from "../requests/graphqlRequestClient";
8+
import useStore from "../store";
9+
import Spinner from "./Spinner";
10+
import PostModal from "./modals/PostModal";
11+
import CreatePost from "./posts/CreatePost";
12+
13+
const Header = () => {
14+
const router = useRouter();
15+
const store = useStore();
16+
const user = store.authUser;
17+
18+
const { refetch } = useLogoutUserQuery(
19+
graphqlRequestClient,
20+
{},
21+
{
22+
enabled: false,
23+
onSuccess() {
24+
queryClient.clear();
25+
router.push("/login");
26+
},
27+
onError(error: any) {
28+
error.response.errors.forEach((err: any) => {
29+
toast(err.message, {
30+
type: "error",
31+
position: "top-right",
32+
});
33+
queryClient.clear();
34+
router.push("/login");
35+
});
36+
},
37+
}
38+
);
39+
40+
const handleLogout = () => {
41+
refetch();
42+
};
43+
44+
return (
45+
<>
46+
<header className="bg-white h-20">
47+
<nav className="h-full flex justify-between container items-center">
48+
<Link href="/" className="text-ct-dark-600 text-2xl font-semibold">
49+
Web
50+
</Link>
51+
<ul className="flex items-center gap-4">
52+
<li>
53+
<Link href="/" className="text-ct-dark-600">
54+
Home
55+
</Link>
56+
</li>
57+
{!user && (
58+
<>
59+
<li>
60+
<Link href="/register" className="text-ct-dark-600">
61+
SignUp
62+
</Link>
63+
</li>
64+
<li>
65+
<Link href="/login" className="text-ct-dark-600">
66+
Login
67+
</Link>
68+
</li>
69+
</>
70+
)}
71+
{!!user && (
72+
<>
73+
<li>
74+
<Link href="/profile" className="text-ct-dark-600">
75+
Profile
76+
</Link>
77+
</li>
78+
<li
79+
className="cursor-pointer"
80+
onClick={() => store.setOpenModal(true)}
81+
>
82+
Create Post
83+
</li>
84+
<li className="cursor-pointer" onClick={handleLogout}>
85+
Logout
86+
</li>
87+
</>
88+
)}
89+
</ul>
90+
</nav>
91+
</header>
92+
<PostModal openModal={store.openModal} setOpenModal={store.setOpenModal}>
93+
<CreatePost />
94+
</PostModal>
95+
<div className="pt-4 pl-2 bg-ct-blue-600 fixed">
96+
{store.pageLoading && <Spinner color="text-ct-yellow-600" />}
97+
</div>
98+
</>
99+
);
100+
};
101+
102+
export default Header;

client/components/LoadingButton.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from "react";
2+
import Spinner from "./Spinner";
3+
4+
interface LoadingButtonProps {
5+
loading: boolean;
6+
btnColor?: string;
7+
textColor?: string;
8+
children: React.ReactNode;
9+
}
10+
11+
const LoadingButton: React.FC<LoadingButtonProps> = ({
12+
textColor = "text-white",
13+
btnColor = "bg-ct-yellow-600",
14+
children,
15+
loading = false,
16+
}) => {
17+
return (
18+
<button
19+
type="submit"
20+
className={`w-full py-3 font-semibold ${btnColor} rounded-lg outline-none border-none flex justify-center ${
21+
loading ? "bg-[#ccc]" : ""
22+
}`}
23+
>
24+
{loading ? (
25+
<div className="flex items-center gap-3">
26+
<Spinner />
27+
<span className="text-slate-500 inline-block">Loading...</span>
28+
</div>
29+
) : (
30+
<span className={`${textColor}`}>{children}</span>
31+
)}
32+
</button>
33+
);
34+
};
35+
36+
export default LoadingButton;

0 commit comments

Comments
 (0)