diff options
| -rw-r--r-- | next.config.js | 6 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | src/components/Alert.js | 16 | ||||
| -rw-r--r-- | src/helpers/mailer.js | 12 | ||||
| -rw-r--r-- | src/pages/activate.js | 110 | ||||
| -rw-r--r-- | src/pages/api/activation-request.js | 31 | ||||
| -rw-r--r-- | src/pages/api/activation.js | 16 | ||||
| -rw-r--r-- | src/pages/api/login.js | 3 | ||||
| -rw-r--r-- | src/pages/api/register.js | 3 | ||||
| -rw-r--r-- | src/pages/login.js | 22 | ||||
| -rw-r--r-- | src/pages/register.js | 30 |
11 files changed, 235 insertions, 15 deletions
diff --git a/next.config.js b/next.config.js index 4435ccba..1897a178 100644 --- a/next.config.js +++ b/next.config.js @@ -14,7 +14,11 @@ const nextConfig = { env: { SELF_HOST: 'http://localhost:3000', ODOO_HOST: 'https://erp.indoteknik.com', - SOLR_HOST: 'http://34.101.189.218:8983' + SOLR_HOST: 'http://34.101.189.218:8983', + MAIL_PORT: 465, + MAIL_HOST: 'smtp.gmail.com', + MAIL_USER: 'sales@indoteknik.com', + MAIL_PASS: '$&nKAoP7!TAm' }, } diff --git a/package.json b/package.json index 15c6802f..b89254af 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "cookies-next": "^2.1.1", "next": "13.0.0", "next-progress": "^2.2.0", + "nodemailer": "^6.8.0", "react": "18.2.0", "react-dom": "18.2.0", "react-lazy-load-image-component": "^1.5.5", diff --git a/src/components/Alert.js b/src/components/Alert.js new file mode 100644 index 00000000..3dddd56e --- /dev/null +++ b/src/components/Alert.js @@ -0,0 +1,16 @@ +const Alert = ({ children, className, type }) => { + let typeClass = ''; + switch (type) { + case 'info': + typeClass = ' bg-blue-200 text-blue-900 ' + break; + case 'success': + typeClass = ' bg-green-200 text-green-900 ' + break; + } + return ( + <div className={"rounded w-full text-medium p-3" + typeClass + className}>{children}</div> + ); +} + +export default Alert;
\ No newline at end of file diff --git a/src/helpers/mailer.js b/src/helpers/mailer.js new file mode 100644 index 00000000..4e7ff7cc --- /dev/null +++ b/src/helpers/mailer.js @@ -0,0 +1,12 @@ +const nodemailer = require('nodemailer'); +const mailer = nodemailer.createTransport({ + port: process.env.MAIL_PORT, + host: process.env.MAIL_HOST, + auth: { + user: process.env.MAIL_USER, + pass: process.env.MAIL_PASS + }, + secure: true +}); + +export default mailer;
\ No newline at end of file diff --git a/src/pages/activate.js b/src/pages/activate.js new file mode 100644 index 00000000..8c4fc9aa --- /dev/null +++ b/src/pages/activate.js @@ -0,0 +1,110 @@ +import axios from "axios"; +import Head from "next/head"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import Alert from "../components/Alert"; +import Spinner from "../components/Spinner"; +import { setAuth } from "../helpers/auth"; +import Logo from "../images/logo.png"; + +export default function Activate() { + const [email, setEmail] = useState(''); + const [isInputFulfilled, setIsInputFulfilled] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [alert, setAlert] = useState(); + const router = useRouter(); + const { token } = router.query; + + useEffect(() => { + if (router.query.email) setEmail(router.query.email); + }, [router]) + + useEffect(() => { + const activateIfTokenExist = async () => { + if (token) { + let activation = await axios.post(`${process.env.SELF_HOST}/api/activation`, {token}); + if (activation.data.activation) { + setAuth(activation.data.user); + setAlert({ + component: <>Selamat, akun anda berhasil diaktifkan, <Link className="text-gray-900" href="/register">kembali ke beranda</Link>.</>, + type: 'success' + }); + } else { + setAlert({ + component: <>Mohon maaf token sudah tidak aktif, lakukan permintaan aktivasi akun kembali atau <Link className="text-gray-900" href="/register">masuk</Link> jika sudah memiliki akun.</>, + type: 'info' + }); + } + } + } + activateIfTokenExist(); + }, [token]); + + useEffect(() => { + setIsInputFulfilled(email != ''); + }, [email]); + + const activationRequest = async (e) => { + e.preventDefault(); + setIsLoading(true); + let activationRequest = await axios.post(`${process.env.SELF_HOST}/api/activation-request`, {email}); + if (activationRequest.data.activation_request) { + setAlert({ + component: <>Mohon cek email anda untuk aktivasi akun Indoteknik</>, + type: 'success' + }); + } else { + switch (activationRequest.data.reason) { + case 'NOT_FOUND': + setAlert({ + component: <>Email tersebut belum terdaftar, <Link className="text-gray-900" href="/register">daftar sekarang</Link>.</>, + type: 'info' + }); + break; + case 'ACTIVE': + setAlert({ + component: <>Email tersebut sudah terdaftar dan sudah aktif, <Link className="text-gray-900" href="/register">masuk sekarang</Link>.</>, + type: 'info' + }); + break; + } + } + setIsLoading(false); + } + return ( + <> + <Head> + <title>Aktivasi Akun Indoteknik</title> + </Head> + <main className="max-w-lg mx-auto flex flex-col items-center px-4 pb-8"> + <Link href="/" className="mt-16"> + <Image src={Logo} alt="Logo Indoteknik" width={165} height={42} /> + </Link> + <h1 className="text-2xl text-gray-900 mt-4 text-center">Aktivasi Akun Indoteknik Anda</h1> + <h2 className="text-gray-800 mt-2 mb-4 text-center">Link aktivasi akan dikirimkan melalui email</h2> + {alert ? ( + <Alert className="text-center" type={alert.type}>{alert.component}</Alert> + ) : ''} + <form onSubmit={activationRequest} className="w-full"> + <input + type="text" + className="form-input bg-gray-100 mt-4 focus:ring-1 focus:ring-yellow-900" + placeholder="johndoe@gmail.com" + value={email} + onChange={(e) => setEmail(e.target.value)} + autoFocus + /> + <button type="submit" disabled={!isInputFulfilled} className="btn-yellow font-semibold mt-4 w-full"> + {isLoading ? ( + <div className="flex justify-center items-center gap-x-2"> + <Spinner className="w-4 h-4 text-gray-600 fill-gray-900" /> <span>Loading...</span> + </div> + ) : 'Kirim Email'} + </button> + </form> + </main> + </> + ) +}
\ No newline at end of file diff --git a/src/pages/api/activation-request.js b/src/pages/api/activation-request.js new file mode 100644 index 00000000..42ad5364 --- /dev/null +++ b/src/pages/api/activation-request.js @@ -0,0 +1,31 @@ +import apiOdoo from "../../helpers/apiOdoo"; +import mailer from "../../helpers/mailer"; + +export default async function handler(req, res) { + try { + const { email } = req.body; + let result = await apiOdoo( + 'POST', + '/api/v1/user/activation-request', + {email} + ); + if (result.activation_request) { + mailer.sendMail({ + from: 'sales@indoteknik.com', + to: result.user.email, + subject: 'Permintaan Aktivasi Akun Indoteknik', + html: ` + <h1>Permintaan Aktivasi Akun Indoteknik</h1> + <br> + <p>Aktivasi akun anda melalui link berikut: <a href="${process.env.SELF_HOST}/activate?token=${result.token}">Aktivasi Akun</a></p> + ` + }); + } + delete result.user; + delete result.token; + res.status(200).json(result); + } catch (error) { + console.log(error); + res.status(400).json({ error: error.message }); + } +}
\ No newline at end of file diff --git a/src/pages/api/activation.js b/src/pages/api/activation.js new file mode 100644 index 00000000..67ec1c9e --- /dev/null +++ b/src/pages/api/activation.js @@ -0,0 +1,16 @@ +import apiOdoo from "../../helpers/apiOdoo"; + +export default async function handler(req, res) { + try { + const { token } = req.body; + let result = await apiOdoo( + 'POST', + '/api/v1/user/activation', + {token} + ); + res.status(200).json(result); + } catch (error) { + console.log(error); + res.status(400).json({ error: error.message }); + } +}
\ No newline at end of file diff --git a/src/pages/api/login.js b/src/pages/api/login.js index cf464817..a747294b 100644 --- a/src/pages/api/login.js +++ b/src/pages/api/login.js @@ -5,12 +5,11 @@ export default async function handler(req, res) { const { email, password } = req.body; let result = await apiOdoo( 'POST', - '/api/v1/auth/login', + '/api/v1/user/login', {email, password} ); res.status(200).json(result); } catch (error) { - console.log(error); res.status(400).json({ error: error.message }); } }
\ No newline at end of file diff --git a/src/pages/api/register.js b/src/pages/api/register.js index 5c21f5ca..323cab24 100644 --- a/src/pages/api/register.js +++ b/src/pages/api/register.js @@ -5,12 +5,11 @@ export default async function handler(req, res) { const { email, name, password } = req.body; let result = await apiOdoo( 'POST', - '/api/v1/auth/register', + '/api/v1/user/register', {email, name, password} ); res.status(200).json(result); } catch (error) { - console.log(error); res.status(400).json({ error: error.message }); } }
\ No newline at end of file diff --git a/src/pages/login.js b/src/pages/login.js index 315146a5..20912242 100644 --- a/src/pages/login.js +++ b/src/pages/login.js @@ -4,7 +4,7 @@ import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { toast } from "react-toastify"; +import Alert from "../components/Alert"; import Spinner from "../components/Spinner"; import { setAuth } from "../helpers/auth"; import Logo from "../images/logo.png"; @@ -15,6 +15,7 @@ export default function Login() { const [password, setPassword] = useState(''); const [isInputFulfilled, setIsInputFulfilled] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [alert, setAlert] = useState(); useEffect(() => { setIsInputFulfilled(email && password); @@ -28,8 +29,20 @@ export default function Login() { setAuth(login.data.user); router.push('/'); } else { - toast.info('Email atau Password tidak cocok'); - toast.clearWaitingQueue(); + switch (login.data.reason) { + case 'NOT_FOUND': + setAlert({ + component: <>Email atau password tidak cocok</>, + type: 'info' + }); + break; + case 'NOT_ACTIVE': + setAlert({ + component: <>Email belum diaktivasi, <Link className="text-gray-900" href={`/activate?email=${email}`}>aktivasi sekarang</Link></>, + type: 'info' + }); + break; + } setIsLoading(false); } } @@ -45,6 +58,9 @@ export default function Login() { </Link> <h1 className="text-2xl text-gray-900 mt-4">Mulai Belanja Sekarang</h1> <h2 className="text-gray-800 mt-2 mb-4">Masuk ke akun kamu untuk belanja</h2> + {alert ? ( + <Alert className="text-center" type={alert.type}>{alert.component}</Alert> + ) : ''} <form onSubmit={login} className="w-full"> <input type="text" diff --git a/src/pages/register.js b/src/pages/register.js index 8c499b32..69099a0b 100644 --- a/src/pages/register.js +++ b/src/pages/register.js @@ -3,7 +3,7 @@ import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; import { useEffect, useState } from "react"; -import { toast } from "react-toastify"; +import Alert from "../components/Alert"; import Spinner from "../components/Spinner"; import Logo from "../images/logo.png"; @@ -13,6 +13,7 @@ export default function Login() { const [password, setPassword] = useState(''); const [isInputFulfilled, setIsInputFulfilled] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [alert, setAlert] = useState(); useEffect(() => { setIsInputFulfilled(email && name && password); @@ -21,14 +22,26 @@ export default function Login() { const register = async (e) => { e.preventDefault(); setIsLoading(true); - let login = await axios.post(`${process.env.SELF_HOST}/api/register`, {email, name, password}); - if (login.data.register) { - toast.success('Berhasil mendaftarkan akun anda, cek email untuk melakukan aktivasi akun'); + let register = await axios.post(`${process.env.SELF_HOST}/api/register`, {email, name, password}); + if (register.data.register) { + setAlert({ + component: <>Berhasil mendaftarkan akun anda, cek email untuk melakukan aktivasi akun</>, + type: 'success' + }); + setEmail(''); + setName(''); + setPassword(''); } else { - toast.info('Email telah digunakan'); - toast.clearWaitingQueue(); - setIsLoading(false); + switch (register.data.reason) { + case 'EMAIL_USED': + setAlert({ + component: <>Email telah digunakan</>, + type: 'info' + }); + break; + } } + setIsLoading(false); } return ( @@ -42,6 +55,9 @@ export default function Login() { </Link> <h1 className="text-2xl text-gray-900 mt-4 text-center">Mudahkan Pembelian dengan Indoteknik</h1> <h2 className="text-gray-800 mt-2 mb-4">Daftar untuk melanjutkan</h2> + {alert ? ( + <Alert className="text-center" type={alert.type}>{alert.component}</Alert> + ) : ''} <form onSubmit={register} className="w-full"> <input type="text" |
