parent
9be964ef8f
commit
9d4019f3e4
@ -1,7 +1,123 @@
|
|||||||
import { Component } from 'solid-js';
|
import { Component, createEffect, createSignal } from 'solid-js';
|
||||||
|
import RestClient from '../api/RestClient';
|
||||||
|
import { setStore, store, User } from '../App';
|
||||||
|
import Button from '../ui/Button';
|
||||||
|
|
||||||
|
type LoginRequest = {
|
||||||
|
login: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [loggedInUser, setLoggedInUser] = createSignal();
|
||||||
|
export { loggedInUser };
|
||||||
|
|
||||||
const Login: Component = () => {
|
const Login: Component = () => {
|
||||||
return <h1 class='text-4xl'>Login</h1>;
|
const [loginRequest, setLoginRequest] = createSignal<LoginRequest>({
|
||||||
|
login: '',
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
const [login, setLogin] = createSignal('');
|
||||||
|
const [password, setPassword] = createSignal('');
|
||||||
|
|
||||||
|
// Populate the current user outside the JSX (we need createEffect for this!)
|
||||||
|
createEffect(async () => {
|
||||||
|
if (loginRequest().login.trim() === '' || loginRequest().password.trim() === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const user = (await RestClient.POST(
|
||||||
|
'/login',
|
||||||
|
JSON.stringify(loginRequest())
|
||||||
|
)) as User;
|
||||||
|
if (user.id === undefined) {
|
||||||
|
console.log(user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStore({
|
||||||
|
...store,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
window.location.href = '/';
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class='grid h-screen place-items-center'>
|
||||||
|
<div class='block p-6 rounded-lg shadow-lg bg-white max-w-sm'>
|
||||||
|
<form>
|
||||||
|
<div class='form-group mb-6'>
|
||||||
|
<label for='username' class='form-label inline-block mb-2 text-gray-700'>
|
||||||
|
Login
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
class='form-control
|
||||||
|
block
|
||||||
|
w-full
|
||||||
|
px-3
|
||||||
|
py-1.5
|
||||||
|
text-base
|
||||||
|
font-normal
|
||||||
|
text-gray-700
|
||||||
|
bg-white bg-clip-padding
|
||||||
|
border border-solid border-gray-300
|
||||||
|
rounded
|
||||||
|
transition
|
||||||
|
ease-in-out
|
||||||
|
m-0
|
||||||
|
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
|
||||||
|
id='username'
|
||||||
|
placeholder='Username'
|
||||||
|
onInput={(e) => setLogin(e.currentTarget.value)}
|
||||||
|
value={login()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class='form-group mb-6'>
|
||||||
|
<label for='Password' class='form-label inline-block mb-2 text-gray-700'>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type='password'
|
||||||
|
class='form-control block
|
||||||
|
w-full
|
||||||
|
px-3
|
||||||
|
py-1.5
|
||||||
|
text-base
|
||||||
|
font-normal
|
||||||
|
text-gray-700
|
||||||
|
bg-white bg-clip-padding
|
||||||
|
border border-solid border-gray-300
|
||||||
|
rounded
|
||||||
|
transition
|
||||||
|
ease-in-out
|
||||||
|
m-0
|
||||||
|
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
|
||||||
|
id='Password'
|
||||||
|
placeholder='Password'
|
||||||
|
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
value={password()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
onClick={() => {
|
||||||
|
console.log(login(), password());
|
||||||
|
setLoginRequest({ login: login(), password: password() });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</Button>
|
||||||
|
<p class='text-gray-800 mt-6 text-center'>
|
||||||
|
Want to track your todo{"'"}s?{' '}
|
||||||
|
<a
|
||||||
|
href='#!'
|
||||||
|
class='text-blue-600 hover:text-blue-700 focus:text-blue-700 transition duration-200 ease-in-out'
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Login;
|
export default Login;
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
import { Component, JSX } from 'solid-js';
|
||||||
|
|
||||||
|
type ButtonProps = {
|
||||||
|
backgroundColor?: string;
|
||||||
|
color?: string;
|
||||||
|
fullWidth?: boolean;
|
||||||
|
onClick?: Function;
|
||||||
|
children?: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const backgroundColors: { [key: string]: string } = {
|
||||||
|
blue: 'bg-sh-blue hover:bg-sh-blueM1 focus:bg-sh-blueM1 active:bg-sh-blueM1',
|
||||||
|
red: 'bg-red-600 hover:bg-red-700 focus:bg-red-700 active:bg-red-800',
|
||||||
|
green: 'bg-green-600 hover:bg-green-700 focus:bg-green-700 active:bg-green-800',
|
||||||
|
yellow: 'bg-sh-yellow hover:bg-sh-yellowM1 focus:bg-sh-yellowM1 active:bg-sh-yellowM1',
|
||||||
|
magenta:
|
||||||
|
'bg-sh-magenta hover:bg-sh-magentaM1 focus:bg-sh-magentaM1 active:bg-sh-magentaM1',
|
||||||
|
gray: 'bg-gray-700 hover:bg-gray-800 focus:bg-gray-800 active:bg-gray-800',
|
||||||
|
cyan: 'bg-cyan-700 hover:bg-cyan-900 focus:bg-cyan-900 active:bg-cyan-800',
|
||||||
|
};
|
||||||
|
|
||||||
|
const colors: { [key: string]: string } = {
|
||||||
|
white: 'text-white',
|
||||||
|
black: 'text-black',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Button: Component<ButtonProps> = (props) => {
|
||||||
|
const backgroundColor = backgroundColors[props.backgroundColor || 'blue'];
|
||||||
|
const color = colors[props.color || 'white'];
|
||||||
|
const fullWidth = props.fullWidth ? 'w-full' : '';
|
||||||
|
const fun =
|
||||||
|
props.onClick !== undefined
|
||||||
|
? (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
(props.onClick as () => void)();
|
||||||
|
}
|
||||||
|
: (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log(
|
||||||
|
'Default behavior of Button component is invoked!',
|
||||||
|
'You might want to change the onClick function?'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const btnStyle = `${color} ${backgroundColor} ${fullWidth} inline-block px-6 py-2.5 font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button class={btnStyle} onClick={fun}>
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Button;
|
@ -0,0 +1,51 @@
|
|||||||
|
import { A } from '@solidjs/router';
|
||||||
|
import { Component, Show } from 'solid-js';
|
||||||
|
import RestClient from '../api/RestClient';
|
||||||
|
import { setStore, store, User } from '../App';
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
|
const Navbar: Component = () => {
|
||||||
|
return (
|
||||||
|
<div class='flex justify-between items-center bg-sh-bg p-3'>
|
||||||
|
<div class='flex items-center'>
|
||||||
|
<a
|
||||||
|
href='/'
|
||||||
|
class='pl-2 text-xl font-bold no-underline text-sh-yellow hover:text-sh-yellowM1'
|
||||||
|
>
|
||||||
|
Just todo it!
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1 class='flex -ml-20 text-xl font-bold no-underline text-sh-yellow'>
|
||||||
|
Hey {store.user.login}!
|
||||||
|
</h1>
|
||||||
|
<Show
|
||||||
|
when={store.user.login !== ''}
|
||||||
|
fallback={
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={() => (window.location.href = '/login')}
|
||||||
|
backgroundColor='yellow'
|
||||||
|
color='black'
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
RestClient.DELETE('/logout');
|
||||||
|
setStore({ todos: [], user: { id: '', login: '' } });
|
||||||
|
window.location.href = '/login';
|
||||||
|
}}
|
||||||
|
backgroundColor='magenta'
|
||||||
|
color='black'
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
@ -0,0 +1,65 @@
|
|||||||
|
import { Component, For, JSX } from 'solid-js';
|
||||||
|
import { Priority, Status, Todo } from '../pages/Home';
|
||||||
|
|
||||||
|
type TableProps = {
|
||||||
|
data: Todo[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type TableRowProps = {
|
||||||
|
children: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TableDataProps = {
|
||||||
|
children?: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TableHeadProps = {
|
||||||
|
children?: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableHead: Component<TableHeadProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<th scope='col' class='text-sm font-medium px-6 py-4'>
|
||||||
|
{props.children}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableData: Component<TableDataProps> = (props) => {
|
||||||
|
return <td class='text-sm text-gray-900 font-light px-6 py-4 '>{props.children}</td>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableRow: Component<TableRowProps> = (props) => {
|
||||||
|
const rowClass = 'border-b';
|
||||||
|
return <tr class={rowClass}>{props.children}</tr>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Table: Component<TableProps> = (props) => {
|
||||||
|
if (props.data.length < 1) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div class='overflow-hidden mt-10 mx-10'>
|
||||||
|
<table class='min-w-full text-center'>
|
||||||
|
<thead class='border-b text-sh-bgM1 bg-sh-yellow'>
|
||||||
|
{Object.keys(props.data[0]).map((key) => (
|
||||||
|
<TableHead>{key}</TableHead>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<For each={props.data}>
|
||||||
|
{(child) => (
|
||||||
|
<TableRow>
|
||||||
|
{Object.values(child).map((val) => (
|
||||||
|
<TableData>{val}</TableData>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Table;
|
@ -1,8 +1,48 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'sh-bgM2': '#000306',
|
||||||
|
'sh-bgM1': '#020E18',
|
||||||
|
'sh-bg': '#0D1F2D',
|
||||||
|
'sh-bgP1': '#1E3141',
|
||||||
|
'sh-bgP2': '#3C5161',
|
||||||
|
'sh-fgM2': '#546A7B',
|
||||||
|
'sh-fgM1': '#9EA3B0',
|
||||||
|
'sh-fg': '#C3C9E9',
|
||||||
|
'sh-fgP1': '#BDD3DD',
|
||||||
|
'sh-white': '#FEFEFE',
|
||||||
|
'sh-black': '#000306',
|
||||||
|
'sh-yellowM1': '#DA7F05',
|
||||||
|
'sh-yellow': '#FDAA3A',
|
||||||
|
'sh-yellowP1': '#FFC16E',
|
||||||
|
'sh-orangeM1': '#C45A00',
|
||||||
|
'sh-orange': '#FF7F11',
|
||||||
|
'sh-orangeP1': '#FFA251',
|
||||||
|
'sh-redM2': '#960004',
|
||||||
|
'sh-redM1': '#CD1419',
|
||||||
|
'sh-red': '#ED474A',
|
||||||
|
'sh-redP1': '#FD7E81',
|
||||||
|
'sh-greenM1': '#739F2F',
|
||||||
|
'sh-green': '#A5CC69',
|
||||||
|
'sh-greenP1': '#D5EEAE',
|
||||||
|
'sh-greenP2': '#A2DFED',
|
||||||
|
'sh-blueM2': '#0683A0',
|
||||||
|
'sh-blueM1': '#36A1BB',
|
||||||
|
'sh-blue': '#64BFD6',
|
||||||
|
'sh-blueP1': '#A2DFED',
|
||||||
|
'sh-magentaM2': '#690635',
|
||||||
|
'sh-magentaM1': '#902B5B',
|
||||||
|
'sh-magenta': '#B95F8A',
|
||||||
|
'sh-magentaP1': '#DBA1BC',
|
||||||
|
'sh-purpleM2': '#630DAE',
|
||||||
|
'sh-purpleM1': '#863FC4',
|
||||||
|
'sh-purple': '#A86CDC',
|
||||||
|
'sh-purpleP1': '#CEA7F0',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in new issue