Commit 090722d6 authored by Nauzet Hernandez's avatar Nauzet Hernandez
Browse files

Initial commit

parents
node_modules
.DS_Store
dist
dist-ssr
*.local
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
This diff is collapsed.
{
"name": "tng-frontend-boilerplate-vue",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"axios": "^0.21.0",
"jose": "^3.2.0",
"jwt-decode": "^3.1.2",
"querystring": "^0.2.0",
"ramda": "^0.27.1",
"vue": "^3.0.4",
"vue-router": "^4.0.0-rc.6"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.4",
"autoprefixer": "^9.8.6",
"postcss": "^7.0.35",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.1",
"vite": "^1.0.0-rc.13"
}
}
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}
<template>
<navigation />
<router-view />
</template>
<script>
import useAuth from './composables/use-auth'
import Navigation from './components/Navigation.vue'
export default {
name: 'App',
components: {
Navigation
},
provide () {
return {
auth: useAuth()
}
}
}
</script>
/*! @import */
@tailwind base;
@tailwind components;
@tailwind utilities;
#app {
@apply h-screen w-screen flex flex-col antialiased;
}
/* Forms */
.form,
.form fieldset {
@apply grid gap-4;
}
.form .field {
@apply flex flex-col gap-1;
}
.form .field label {
@apply font-semibold text-sm;
}
input,
textarea,
select {
@apply block rounded-lg px-4 py-2;
}
select {
@apply relative pr-10;
background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22currentColor%22%3E%0A%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.293%207.293a1%201%200%20011.414%200L10%2010.586l3.293-3.293a1%201%200%20111.414%201.414l-4%204a1%201%200%2001-1.414%200l-4-4a1%201%200%20010-1.414z%22%20clip-rule%3D%22evenodd%22%2F%3E%0A%3C%2Fsvg%3E');
background-repeat: no-repeat;
background-position: right 0.5rem top 50%, 0 0;
background-size: 2rem 100%;
}
input:focus,
textarea:focus,
select:focus {
@apply shadow outline-none;
}
.btn {
@apply px-4 py-2 rounded font-bold flex items-center justify-center;
}
/* Theme */
input,
textarea,
select {
@apply bg-gray-100;
}
.btn {
@apply bg-gray-200 text-gray-600;
}
.btn-primary {
@apply bg-blue-800 text-white;
}
<template>
<!-- Checkbox Button -->
<label class="checkbox cursor-pointer">
<!-- input -->
<input
:name="name"
:id="id"
type="checkbox"
class="hidden"
@change.stop="$emit('change', $event.target.checked)"
:checked="checked"
/>
<svg
v-if="checked"
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
/>
</svg>
<svg
v-else
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<circle cx="12" cy="12" r="9" />
</svg>
</label>
</template>
<script>
export default {
name: 'Checkbox',
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
name: String,
id: String
}
}
</script>
<template>
<header class="navigation bg-blue-900 flex relative">
<!-- App bar -->
<div class="flex align-middle text-white px-4 py-3">
<!-- Toggle button -->
<button @click="appBar.toggle" class="px-2 focus:outline-none">
<svg
v-if="appBar.isEnabled.value"
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
<svg
v-else
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd"
/>
</svg>
</button>
<!-- Title -->
<h2
class="text-xl font-bold flex-shrink-0 md:text-lg collapsable-md mx-2"
>
{{ title }}
</h2>
</div>
<!-- Navigation -->
<nav
class="bg-blue-800 text-blue-100 flex-grow px-2 py-3 absolute top-full w-full"
:class="{
block: appBar.isEnabled.value,
hidden: !appBar.isEnabled.value
}"
>
<!-- Links -->
<div>
<router-link
class="flex gap-4 items-center w-full px-4 py-2 mb-1 rounded font-semibold"
to="/"
><svg
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"
/></svg
><span>Home</span>
</router-link>
<hr class="border-blue-700 my-3" />
<div
class="flex gap-4 items-center w-full px-4 py-2 mb-1 rounded font-semibold"
>
<svg
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z"
clip-rule="evenodd"
/>
</svg>
<span>{{ auth.user.value.name }}</span>
</div>
<router-link
v-if="auth.isAuthenticated.value"
class="flex gap-4 items-center w-full px-4 py-2 mb-1 rounded font-semibold"
to="/logout"
>
<svg
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3 3a1 1 0 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414 1.414l3-3a1 1 0 000-1.414l-3-3a1 1 0 10-1.414 1.414L14.586 9H7a1 1 0 100 2h7.586l-1.293 1.293z"
clip-rule="evenodd"
/></svg
><span>Logout</span>
</router-link>
<router-link
v-else
class="flex gap-4 items-center w-full px-4 py-2 mb-1 rounded font-semibold"
to="/login"
><svg
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3 3a1 1 0 011 1v12a1 1 0 11-2 0V4a1 1 0 011-1zm7.707 3.293a1 1 0 010 1.414L9.414 9H17a1 1 0 110 2H9.414l1.293 1.293a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
<span>Login</span>
</router-link>
</div>
</nav>
</header>
</template>
<script>
import useToggle from '../composables/use-toggle'
export default {
name: 'Navigation',
props: {
title: {
type: String,
default: 'TNG App'
}
},
inject: ['auth'],
setup () {
const appBar = useToggle()
return { appBar }
}
}
</script>
<template>
<div
class="notification flex items-center justify-between text-sm font-bold p-4"
:class="color"
>
<span>{{ message }}</span>
<button @click.prevent="$emit('close')">
<svg
class="w-5 h-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
</template>
<script>
export default {
name: 'Notification',
props: {
message: String,
status: String
},
components: { XIcon },
computed: {
color () {
switch (this.status) {
case 'success':
return 'bg-green-300 text-green-700'
case 'error':
case 'danger':
return 'bg-red-300 text-red-700'
case 'warning':
return 'bg-yellow-300 text-yellow-700'
default:
return 'bg-gray-300 text-gray-700'
}
}
}
}
</script>
<template>
<div class="pagination">
<!-- First -->
<button
@click="$emit('page-select', 1)"
:class="{ current: current === 1 }"
:disabled="current === 1"
>
1
</button>
<!-- Separator -->
<span class="separator" v-if="current > 4" />
<!-- Range -->
<button
:class="{ current: page === current }"
:disabled="page === current"
v-for="page in currentRange"
:key="page"
@click="$emit('page-select', page)"
>
{{ page }}
</button>
<!-- Separator -->
<span class="separator" v-if="current <= pages - 4" />
<!-- Last -->
<button
v-if="pages > 1"
@click="$emit('page-select', pages)"
:class="{ current: current === pages }"
:disabled="current === pages"
>
{{ pages }}
</button>
</div>
</template>
<script>
export default {
name: 'Pagination',
props: {
size: {
type: Number,
required: true
},
current: {
type: Number,
default: 1
},
total: {
type: Number,
required: true
}
},
computed: {
pages () {
if (!this.total) return 0
if (this.total < this.size) return 1
return (
Math.floor(this.total / this.size) +
(this.total % this.size > 0 ? 1 : 0)
)
},
currentRange () {
const r = []
const left = Math.max(2, this.current - 2)
const right = Math.min(this.pages - 1, this.current + 2)
for (let i = left; i <= right; i++) {
r.push(i)
}
return r
}
}
}
</script>
<style lang="postcss">
.pagination {
@apply flex items-center justify-center gap-2;
}
.pagination button {
@apply w-8 h-8 flex items-center justify-center rounded-lg bg-primary-200;
}
.pagination button:hover {
@apply bg-primary-300;
}
.pagination button.current {
@apply bg-primary-400 text-white cursor-default;
}
.pagination .separator {
@apply grid w-8 h-8;
place-items: center;
}
.pagination .separator::before {
content: '...';
}
</style>
<template>
<div class="spinner grid items-center justify-center">
<div class="half-circle-spinner">
<div class="circle circle-1"></div>
<div class="circle circle-2"></div>
</div>
</div>
</template>
<style>
.spinner {
width: 60px;
height: 60px;
}
.half-circle-spinner,
.half-circle-spinner * {
box-sizing: border-box;
}
.half-circle-spinner {
width: inherit;
height: inherit;
border-radius: 100%;
position: relative;
border-color: inherit;
}
.half-circle-spinner .circle {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 100%;
border: 6px solid transparent;
}
.half-circle-spinner .circle.circle-1 {
border-top-color: inherit;
animation: half-circle-spinner-animation 1s infinite;
}
.half-circle-spinner .circle.circle-2 {
border-bottom-color: inherit;
animation: half-circle-spinner-animation 1s infinite alternate;
}
@keyframes half-circle-spinner-animation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<template>
<!-- Toggle Button -->
<label class="toggle flex items-center cursor-pointer">
<!-- toggle -->
<div class="relative">
<!-- input -->
<input
:name="name"
:id="id"
type="checkbox"
class="hidden"
@change="$emit('change', $event.target.checked)"
:checked="checked"
/>
<!-- line -->
<div
class="toggle__line w-10 h-4 bg-gray-500 rounded-full shadow-inner opacity-50"
/>
<!-- dot -->
<div
class="toggle__dot absolute rounded-full w-6 h-6 shadow inset-y-0 left-0 bg-gray-100"
/>
</div>
</label>
</template>
<script>
export default {
name: 'Toggle',
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
name: String,
id: String
}
}
</script>
<style>
.toggle__dot {
top: -0.25rem;
left: -0.25rem;
transition: all 0.3s ease-in-out;
}
input:checked ~ .toggle__dot {
transform: translateX(100%);
background-color: currentColor;
}
</style>
<template>
<form class="form login-form" @submit.prevent="submit">
<fieldset :disabled="isLoading">
<!-- Inputs -->
<div class="field">
<label for="username">Username</label>
<input
class="block w-full mt-1"
type="text"
name="username"
v-model="form.username"
autofocus
/>