Skip to content
This repository has been archived by the owner on Oct 6, 2021. It is now read-only.

Commit

Permalink
Merge branch 'main' into readme/add-new-api-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
zainfathoni committed Aug 11, 2021
2 parents ab352ed + b9a0cda commit 7abe948
Show file tree
Hide file tree
Showing 22 changed files with 611 additions and 65 deletions.
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/epic.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Epic
about: A master issue thread which contains other smaller issues.
title: ""
labels: epic
assignees: zainfathoni
---

## Overview
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Silacak

This is a revamp attemp of Silacak app, built using [Next.js](https://nextjs.org/).
This is a revamp attempt of Silacak app, built using [Next.js](https://nextjs.org/).

## Requirement Specifications

Expand Down
35 changes: 32 additions & 3 deletions __tests__/pages/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
import React from "react";

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import HomePage from "~/pages";
import { authBuilder } from "~/components/layout/home/__mocks__/builders/auth";

jest.mock("next/router", () => require("next-router-mock"));

describe("HomePage", () => {
// TODO: add proper tests later
it("renders without crashing", () => {
it("renders the illustration image correctly", () => {
render(<HomePage />);

expect(screen.getByText(/Welcome to/i)).toBeVisible();
expect(screen.getByText(/selamat datang di layanan/i)).toBeVisible();

expect(
screen.getByRole("img", {
name: /ilustrasi silacak/i,
})
).toBeVisible();
});

it("submits form correctly", () => {
const { username, password } = authBuilder();

render(<HomePage />);

userEvent.type(
screen.getByRole("textbox", {
name: /nama id yang terdaftar \/ user name/i,
}),
username
);

userEvent.type(screen.getByLabelText(/kata sandi \/ password/i), password);

// const consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {});

userEvent.click(screen.getByRole("button", { name: /masuk/i }));

// expect(consoleLogSpy).toHaveBeenCalledTimes(1);
// expect(consoleLogSpy).toHaveBeenCalledWith(username, password);
});
});
13 changes: 13 additions & 0 deletions components/layout/home/__mocks__/builders/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { build, fake } from "@jackfranklin/test-data-bot";

type Auth = {
username: string;
password: string;
};

export const authBuilder = build<Auth>({
fields: {
username: fake(f => f.internet.userName()),
password: fake(f => f.internet.password()),
},
});
31 changes: 31 additions & 0 deletions components/layout/home/__tests__/login-form.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { LoginForm } from "../login-form";
import { authBuilder } from "../__mocks__/builders/auth";

describe("LoginForm", () => {
it("submits form correctly", () => {
const handleSubmit = jest.fn();
const { username, password } = authBuilder();

render(<LoginForm onSubmit={handleSubmit} />);

userEvent.type(
screen.getByRole("textbox", {
name: /nama id yang terdaftar \/ user name/i,
}),
username
);

userEvent.type(screen.getByLabelText(/kata sandi \/ password/i), password);

userEvent.click(screen.getByRole("button", { name: /masuk/i }));

expect(handleSubmit).toHaveBeenCalledTimes(1);
expect(handleSubmit).toHaveBeenCalledWith({
username,
password,
});
});
});
8 changes: 4 additions & 4 deletions components/layout/home/home-navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function HomeNavbar() {
<Disclosure as="nav" className="bg-white">
{({ open }) => (
<>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto px-4 md:px-6 lg:px-8">
<div className="flex justify-between h-20">
<div className="flex">
<div className="flex-shrink-0 flex items-center">
Expand All @@ -47,7 +47,7 @@ export function HomeNavbar() {
width={115}
/>
</div>
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
<div className="hidden md:ml-6 md:flex md:space-x-8">
{homepageNavbarItems.map(item => {
const isActive = item.exact
? item.href === router.asPath
Expand Down Expand Up @@ -75,7 +75,7 @@ export function HomeNavbar() {
})}
</div>
</div>
<div className="-mr-2 flex items-center sm:hidden">
<div className="-mr-2 flex items-center md:hidden">
{/* Mobile menu button */}
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-silacak-500">
<span className="sr-only">Open main menu</span>
Expand All @@ -89,7 +89,7 @@ export function HomeNavbar() {
</div>
</div>

<Disclosure.Panel className="sm:hidden">
<Disclosure.Panel className="md:hidden">
<div className="pt-2 pb-3 space-y-1">
{homepageNavbarItems.map(item => {
const isActive = item.exact
Expand Down
1 change: 1 addition & 0 deletions components/layout/home/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./layout-root";
export * from "./login-form";
72 changes: 72 additions & 0 deletions components/layout/home/login-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useState, FormEvent } from "react";
import { FormLabel, InputText } from "~/components/ui/forms";

export interface LoginFormValues {
username: string;
password: string;
}

export interface LoginFormProps {
initialProps?: LoginFormValues;
onSubmit?: (values: LoginFormValues) => void;
}

export function LoginForm({ onSubmit }: LoginFormProps) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");

const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

if (onSubmit) {
onSubmit({ username, password });
}
};

return (
<form action="#" className="space-y-4" method="POST" onSubmit={handleSubmit}>
<div>
<FormLabel className="block text-sm font-medium text-gray-700" htmlFor="username">
Nama ID yang terdaftar / User Name
</FormLabel>
<div className="mt-1">
<InputText
autoComplete="off"
id="username"
name="username"
onChange={e => setUsername(e.target.value)}
required
type="text"
value={username}
/>
</div>
</div>

<div>
<FormLabel className="block text-sm font-medium text-gray-700" htmlFor="password">
Kata Sandi / Password
</FormLabel>
<div className="mt-1">
<InputText
autoComplete="current-password"
id="password"
name="password"
onChange={e => setPassword(e.target.value)}
required
type="password"
value={password}
/>
</div>
</div>

<div>
<button
className="flex items-center justify-center w-full px-4 py-2 border border-transparent text-center text-base font-medium rounded-md shadow-sm text-white bg-silacak-600 hover:bg-silacak-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-silacak-500"
type="submit"
>
Masuk
</button>
</div>
</form>
);
}
15 changes: 15 additions & 0 deletions components/ui/forms/__tests__/input-text.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { render } from "@testing-library/react";
import { InputText } from "../input-text";

describe("InputText", () => {
it("renders correctly", () => {
const { container } = render(<InputText />);

expect(container.firstChild).toMatchInlineSnapshot(`
<input
class="shadow-sm rounded-md focus:ring-silacak-500 focus:border-silacak-500 block w-full text-sm border-gray-300"
type="text"
/>
`);
});
});
15 changes: 15 additions & 0 deletions components/ui/forms/__tests__/input-textarea.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { render } from "@testing-library/react";
import { InputTextarea } from "../input-textarea";

describe("InputText", () => {
it("renders correctly", () => {
const { container } = render(<InputTextarea />);

expect(container.firstChild).toMatchInlineSnapshot(`
<textarea
class="shadow-sm focus:ring-silacak-500 focus:border-silacak-500 block w-full text-sm border-gray-300 rounded-md"
rows="4"
/>
`);
});
});
17 changes: 17 additions & 0 deletions components/ui/forms/form-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from "react";

import clsx from "clsx";

type FormLabelProps = React.HTMLAttributes<HTMLDivElement>;

export const FormGroup = React.forwardRef<HTMLDivElement, FormLabelProps>(
({ className, children }, ref) => {
return (
<div className={clsx("flex rounded-md shadow-sm", className)} ref={ref}>
{children}
</div>
);
}
);

FormGroup.displayName = "FormGroup";
20 changes: 20 additions & 0 deletions components/ui/forms/form-label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from "react";

import clsx from "clsx";

type FormLabelProps = React.LabelHTMLAttributes<HTMLLabelElement>;

export const FormLabel = React.forwardRef<HTMLLabelElement, FormLabelProps>(
({ className, style, children, htmlFor, ...rest }, ref) => (
<label
className={clsx("block text-sm font-medium text-gray-700", className)}
htmlFor={htmlFor}
ref={ref}
{...rest}
>
{children}
</label>
)
);

FormLabel.displayName = "FormLabel";
6 changes: 6 additions & 0 deletions components/ui/forms/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./form-group";
export * from "./form-label";
export * from "./input-checkbox";
export * from "./input-select";
export * from "./input-text";
export * from "./input-textarea";
23 changes: 23 additions & 0 deletions components/ui/forms/input-checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from "react";

import clsx from "clsx";

type InputCheckboxProps = React.InputHTMLAttributes<HTMLInputElement>;

export const InputCheckbox = React.forwardRef<HTMLInputElement, InputCheckboxProps>(
({ className, ...rest }, ref) => {
return (
<input
className={clsx(
"focus:ring-silacak-500 h-4 w-4 text-silacak-600 border-gray-300 rounded",
className
)}
ref={ref}
type="checkbox"
{...rest}
/>
);
}
);

InputCheckbox.displayName = "InputCheckbox";
26 changes: 26 additions & 0 deletions components/ui/forms/input-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react";

import clsx from "clsx";

interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
block?: boolean;
}

export const InputSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
({ className, style, children, block, ...rest }, ref) => (
<select
className={clsx(
block ? "block" : "inline-block",
"shadow-sm focus:ring-silacak-500 focus:border-silacak-500 w-full text-sm border-gray-300 rounded-md",
className
)}
ref={ref}
style={style}
{...rest}
>
{children}
</select>
)
);

InputSelect.displayName = "InputSelect";
26 changes: 26 additions & 0 deletions components/ui/forms/input-text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react";

import clsx from "clsx";

interface InputTextProps extends React.InputHTMLAttributes<HTMLInputElement> {
isGroupItem?: boolean;
}

export const InputText = React.forwardRef<HTMLInputElement, InputTextProps>(
({ className, type = "text", isGroupItem, ...rest }, ref) => {
return (
<input
className={clsx(
isGroupItem ? "first:rounded-l-md last:rounded-r-md" : "shadow-sm rounded-md",
"focus:ring-silacak-500 focus:border-silacak-500 block w-full text-sm border-gray-300",
className
)}
ref={ref}
type={type}
{...rest}
/>
);
}
);

InputText.displayName = "InputText";
Loading

0 comments on commit 7abe948

Please sign in to comment.