diff --git a/backend/main/migrations/0004_auto_20200407_0015.py b/backend/main/migrations/0004_auto_20200407_0015.py new file mode 100644 index 0000000000000000000000000000000000000000..1664a30a03468c13bf6e6ae05321a29c1dabd436 --- /dev/null +++ b/backend/main/migrations/0004_auto_20200407_0015.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.3 on 2020-04-06 17:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0003_merge_20200309_0633'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='blood_type', + field=models.CharField(blank=True, max_length=3), + ), + migrations.AlterField( + model_name='profile', + name='married_status', + field=models.CharField(blank=True, max_length=15), + ), + ] diff --git a/backend/main/models.py b/backend/main/models.py index 4e22db7fe3c9422ba7b5a050e49e7e6dac457475..ba2e76aa6fdb49186c9d54cb8d3908661ad3fab4 100644 --- a/backend/main/models.py +++ b/backend/main/models.py @@ -71,8 +71,8 @@ class Profile(models.Model): birthdate = models.DateField(null=True) sex = models.CharField(max_length=1, blank=True) profession = models.CharField(max_length=140, blank=True) - blood_type = models.CharField(max_length=8, blank=True) - married_status = models.CharField(max_length=8, blank=True) + blood_type = models.CharField(max_length=3, blank=True) + married_status = models.CharField(max_length=15, blank=True) address = models.CharField(max_length=140, blank=True) city = models.CharField(max_length=60, blank=True) diff --git a/backend/main/serializers.py b/backend/main/serializers.py index 8be01eb3f39c84d351884c6cbc3106e9925ffa0d..9f7d0383eda80abd1bd3629a6446990123131a35 100644 --- a/backend/main/serializers.py +++ b/backend/main/serializers.py @@ -95,6 +95,15 @@ class RegistrationFullSerializer(RegistrationSerializer): def create(self, validated_data): user = super().create(validated_data) + profile_data = validated_data['profile'] + for field in ('body_weight', 'id_card_no', 'birthplace', 'birthdate', 'sex', + 'profession', 'blood_type', 'married_status', 'address', + 'city', 'district', 'village', 'phone_no', 'work_address', + 'work_email', 'work_phone_no'): + value = profile_data.get(field) + setattr(user.profile, field, value) + + user.profile.save() return user class Meta: @@ -102,7 +111,10 @@ class RegistrationFullSerializer(RegistrationSerializer): fields = ['email', 'password', 'first_name', 'profile'] extra_kwargs = { 'password': { - 'write_only': True + 'write_only': True, + 'style': { + 'input_type': 'password' + }, }, 'first_name': { 'required': True diff --git a/backend/main/test_views.py b/backend/main/test_views.py index fa12759989c97b4fe76c43456a8831eec74c5c55..955ecac675bcf37fe56ee19db2213c8cfcb57199 100644 --- a/backend/main/test_views.py +++ b/backend/main/test_views.py @@ -141,8 +141,8 @@ class RegisterFullAPITestCase(APITestCase): self.assertEqual(response.status_code, status.HTTP_201_CREATED) user = User.objects.get(email=data['email']) - self.assertEqual(user.first_name, response.data['first_name']) - self.assertEqual(user.profile.id_card_no, response.data['profile']['id_card_no']) + self.assertEqual(user.first_name, data['first_name']) + self.assertEqual(user.profile.id_card_no, data['profile']['id_card_no']) class AccessTokenAPITestCase(APITestCase): diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d5595d30b325d30d480c50223d3007f8f05cd84c..b96373ea3e5b0250458bed570947d6866c279a65 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7480,6 +7480,11 @@ "readable-stream": "^2.3.6" } }, + "fn-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-3.0.0.tgz", + "integrity": "sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==" + }, "follow-redirects": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", @@ -13079,6 +13084,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lodash-es": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", + "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -15834,6 +15844,11 @@ } } }, + "property-expr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.2.tgz", + "integrity": "sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g==" + }, "protocols": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz", @@ -18580,6 +18595,11 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "synchronous-promise": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.10.tgz", + "integrity": "sha512-6PC+JRGmNjiG3kJ56ZMNWDPL8hjyghF5cMXIFOKg+NiwwEZZIvxTWd0pinWKyD227odg9ygF8xVhhz7gb8Uq7A==" + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -20545,6 +20565,40 @@ "integrity": "sha512-9SNQpwuEh2NucU83i2KMZnONVudZ86YNcFk9tq74YaqrQfgJWO3yB9uzH1tAg8iqh5c9F5j0wuyJ2z72wcum2w==", "optional": true }, + "yup": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.28.3.tgz", + "integrity": "sha512-amVkCgFWe5bGjrrUiODkbIzrSwtB8JpZrQYSrfj2YsbRdrV+tn9LquWdZDlfOx2HXyfEA8FGnlwidE/bFDxO7Q==", + "requires": { + "@babel/runtime": "^7.8.7", + "fn-name": "~3.0.0", + "lodash": "^4.17.15", + "lodash-es": "^4.17.11", + "property-expr": "^2.0.0", + "synchronous-promise": "^2.0.10", + "toposort": "^2.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" + }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + } + } + }, "yurnalist": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/yurnalist/-/yurnalist-1.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4925381e14c6812617e1dac4062e2920cf7f0f54..3da283e95af962d74740b871111edfa5b08dbe70 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,9 +33,10 @@ "react-dom": "^16.12.0", "react-google-login": "^5.1.1", "react-helmet": "^5.2.1", - "react-phone-input-2": "^2.12.0", "react-hook-form": "^5.0.1", - "redux": "^4.0.5" + "react-phone-input-2": "^2.12.0", + "redux": "^4.0.5", + "yup": "^0.28.3" }, "devDependencies": { "@testing-library/jest-dom": "^5.1.1", diff --git a/frontend/src/api.js b/frontend/src/api.js index 199d6f774843843e4afc49c41702d3f8f7607b22..3853924907b12f4d6515931797fff6eb44965fd2 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -26,3 +26,18 @@ export const postUserLogout = () => detail: "SUCCESS", }, }) + +export const postRegisterUser = async () => + await fetch(`${BASE_API_URL}/auth/register-full/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: data, + }) + .then(async response => { + return await response.json() + }) + .then(async data => { + return data + }) diff --git a/frontend/src/components/complete-profile.js b/frontend/src/components/complete-profile.js index 4a6d92bd28063542cb14c0c0b6fd3e201011646b..e18ca9bab6e43d6bff1c1b7e855cf072d9ccd8c3 100644 --- a/frontend/src/components/complete-profile.js +++ b/frontend/src/components/complete-profile.js @@ -1,264 +1,504 @@ -import React, { Component } from "react" -import { Modal, Button, Form, Row, Col, Alert } from "react-bootstrap" +import React from "react" +import { Modal, Button, Form, Row, Col } from "react-bootstrap" +import { Controller } from "react-hook-form" import PhoneInput from "react-phone-input-2" import "react-phone-input-2/lib/style.css" import "./user-form.scss" -function CompleteProfile(props) { +function CompleteProfile({ + show, + handleClose, + control, + register, + errors, + handleSubmit, + onSubmit, +}) { return ( - -
-

- - Lengkapi Profil - -

- -
+ + + +

+ Lengkapi Profil +

+
-

- - Informasi Pribadi - +

+ Informasi Pribadi

-
- - + + +
+
No. KTP/SIM/Passport
- - - - - - +
+
+ + {errors.id_card_no && ( + + {errors.id_card_no.message} + + )} +
+
+ + +
+
Nama Lengkap
- - - - - - +
+
+ + {errors.first_name && ( + + {errors.first_name.message} + + )} +
+
+ + +
+
Tempat Lahir
- - - - - - +
+
+ + {errors.birthplace && ( + + {errors.birthplace.message} + + )} +
+
+ + +
+
Tanggal Lahir
- - - - - - +
+
+ + {errors.birthdate && ( + + {errors.birthdate.message} + + )} +
+
+ + +
+
Berat Badan
- - - - - - -
Jenis Kelamin
-
- -
-
- -
-
- -
+
+
+ + {errors.body_weight && ( + + {errors.body_weight.message} + + )} +
+
+ + + +
Jenis Kelamin
+
+ +
+
+
- - - - +
+ +
+
+ {errors.sex && ( + + {errors.sex.message} + + )} + +
+ + +
+
Pekerjaan
- -
- -
- - - - +
+
+ + + + + + + + + + {errors.profession && ( + + {errors.profession.message} + + )} +
+
+ + +
+
Gol. Darah
- -
- -
- - - - +
+
+ + + + + + + {errors.blood_type && ( + + {errors.blood_type.message} + + )} +
+
+ + +
+
Status Perkawinan
- -
- -
- - -

- - Alamat - -

- - -
Alamat Rumah
-
- - - - - - - - - - - - - - - - - - -
- - +
+
+ + + + + + + {errors.married_status && ( + + {errors.married_status.message} + + )} +
+
+ +

+ Alamat +

+ + + +
Alamat Rumah
+
+ + + + + + + {errors.address && ( + + {errors.address.message} + + )} + + + + + + + + + + + + {errors.district && ( + + {errors.district.message} + + )} + {errors.village && ( + + {errors.village.message} + + )} + {errors.city && ( + + {errors.city.message} + + )} + +
+ + +
+
Alamat Kantor
- - - - -

- - Kontak - -

- - -
No. Telpon/Email Kantor
+
+
+ + {errors.work_address && ( + + {errors.work_address.message} + + )} +
+
+ +

+ Kontak +

+ + +
+ +
No. Telepon/HP
- - - - - - -
No. Telpon/HP
+
+
+ + } + name="phone_no" + control={control} + /> + {errors.phone_no && ( + + {errors.phone_no.message} + + )} +
+
+ + +
+ +
Email Kantor
- - - - - +
+
+ + {errors.work_email && ( + + {errors.work_email.message} + + )} +
+
+ + +
+ +
No. Telepon Kantor
+
+
+
+ + } + name="work_phone_no" + control={control} + /> + {errors.work_phone_no && ( + + {errors.work_phone_no.message} + + )} +
+
+ +
+ +
- - - - ) } diff --git a/frontend/src/components/complete-profile.test.js b/frontend/src/components/complete-profile.test.js deleted file mode 100644 index 80a846b724f12d9fb7b3b0bac7376c78ccce481d..0000000000000000000000000000000000000000 --- a/frontend/src/components/complete-profile.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react" -import { cleanup, fireEvent, render } from "@testing-library/react" -import CompleteProfile from "./complete-profile" - -it("captures clicks", async () => { - const { findByText } = render() - expect(await findByText("Lengkapi Profil")).toBeInTheDocument() - const next = await findByText("Simpan") - fireEvent.click(next) -}) - -afterEach(cleanup) diff --git a/frontend/src/components/modal-register.js b/frontend/src/components/modal-register.js new file mode 100644 index 0000000000000000000000000000000000000000..81967ff48068531d95d19d674e6a921b703cd6ae --- /dev/null +++ b/frontend/src/components/modal-register.js @@ -0,0 +1,117 @@ +import React from "react" +import { Form } from "react-bootstrap" +import { useForm } from "react-hook-form" +import * as yup from "yup" +import { BASE_API_URL } from "../config" + +import ModalRegisterAccount from "./register-account" + +const ModalRegister = ({ show, handleClose }) => { + const schema = yup.object().shape({ + email: yup + .string() + .email("Masukkan email yang valid.") + .required("Masukkan email."), + password: yup + .string() + .min(8, "Password terlalu pendek - setidaknya harus 8 karakter.") + .matches( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})/, + "Password harus terdiri dari minimal 1 huruf kecil, 1 huruf kapital, 1 angka, dan 1 karakter spesial (!@#$%^&*)." + ) + .required("Masukkan password."), + passwordConfirmation: yup + .string() + .oneOf([yup.ref("password"), null], "Password harus sama.") + .required("Masukkan konfirmasi password."), + first_name: yup + .string() + .matches( + /^(?=^.{5,70}$)^[A-Za-z]+([\ A-Za-z]+)*$/, + "Masukkan nama yang valid." + ) + .required("Masukkan nama."), + body_weight: yup + .number() + .positive("Masukkan berat badan yang valid.") + .moreThan(1, "Masukkan berat badan yang valid.") + .lessThan(200, "Masukkan berat badan yang valid.") + .required("Masukkan berat badan."), + id_card_no: yup + .string() + .matches(/^\d{16}$/, "Masukkan nomor kartu pengenal yang valid.") + .required("Masukkan nomor kartu pengenal."), + birthplace: yup.string().required("Masukkan tempat kelahiran."), + // birthdate: yup.date().required("Masukkan tanggal lahir"), + sex: yup.string().required("Pilih jenis kelamin."), + address: yup.string().required("Masukkan alamat."), + city: yup.string().required("Masukkan nama kota."), + district: yup.string().required("Masukkan nama kecamatan."), + village: yup.string().required("Masukkan nama kelurahan."), + phone_no: yup.number().required("Masukkan nomor telepon."), + work_address: yup.string().required("Masukkan alamat kantor."), + work_email: yup + .string() + .email() + .required("Masukkan email kantor."), + work_phone_no: yup.number().required("Masukkan nomor telepon kantor."), + }) + + const { + control, + register, + handleSubmit, + errors, + triggerValidation, + } = useForm({ + mode: "onChange", + validationSchema: schema, + }) + const onSubmit = async data => { + data = + '{"email": "' + + data.email + + '", "password": "' + + data.password + + '", "first_name": "' + + data.first_name + + '", "profile": ' + + JSON.stringify(data) + + "}" + + await fetch(`${BASE_API_URL}/auth/register-full/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: data, + }) + .then(async response => { + return await response.json() + }) + .then(async data => { + await window.alert( + "Silakan verifikasi email terlebih dahulu di" + + data.email + + " untuk login." + ), + await location.reload() + }) + } + return ( + <> + + + ) +} + +export default ModalRegister diff --git a/frontend/src/components/navbar.js b/frontend/src/components/navbar.js index 7584c08afe7affd8f5c4637ef6f9666afa39761c..f8461501372a6db1cf77ef2d187c6c284e5c5407 100644 --- a/frontend/src/components/navbar.js +++ b/frontend/src/components/navbar.js @@ -2,6 +2,7 @@ import React, { useState } from "react" import { Navbar as BNavbar, Nav, Button } from "react-bootstrap" import { Link as GatsbyLink } from "gatsby" import ModalLogin from "./modal-login" +import ModalRegister from "./modal-register" import "./navbar.scss" import { useAuth } from "../hooks/authenticate" @@ -13,6 +14,7 @@ const NavLink = ({ className = "", ...props }) => ( const Navbar = () => { const { user, logout } = useAuth() const [showModalLogin, setShowModalLogin] = useState(false) + const [showModalRegister, setShowModalRegister] = useState(false) return ( <> @@ -29,7 +31,7 @@ const Navbar = () => { Home Jadwal Donor Ajukan Acara Donor - Profil + {user ? Profil : <>} {user ? (