In this course, we’ll be:
- Build an app in Nuxt 3 from scratch
- Use new Nuxt UI component library to scaffold your UI
- Learn how to implement your own drag and drop from scratch
To get the most out of this course, it’s recommended to have familiarity with following:
- Vue 3 / Nuxt 3 fundamentals
- Composition API
- State management with Pinia
- Tailwind CSS (Utility CSS)
https://github.com/Code-Pop/build-trello-clone-with-nuxt-3/blob/04-end/pages/index.vue
https://www.vuemastery.com/courses/build-a-trello-clone-w-nuxt-3/crud-for-columns
project setup
npx nuxi@latest init tello-app
cd trello-app
npm install
npm run dev
ap[p.vue
<template>
<div>
<NuxtWelcome />
</div>
</template>
create pages/index.vue
in app.vue
<script setup >
</script>
<template>
<h1> build vue </h1>
</template>
pinia
npm install pinia @pinia/nuxt
nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@pinia/nuxt'],
})
create
stores/boardStore.js
import { defineStore } from "pinia";
export const useBoardStore = defineStore('boardStore', ()=> {});
create data folder
data/board.json
{
"name": "task-board",
"columns": [
{
"name": "todo",
"tasks": [
{
"id": "",
"name": "",
"description": ""
}
]
}
]
}
text pastry
{
"name": "task-board",
"columns": [
{
"name": "todo",
"tasks": [
{
"id": "45s454",
"name": "",
"description": "d"
}
]
},
{
"name": "in progress",
"tasks": [
{
"id": "444456",
"name": "Task One",
"description": "db"
},
{
"id": "5",
"name": "Task Four",
"description": "dc"
}
]
},
{
"name": "complete",
"tasks": [
{
"id": "6",
"name": "Task Two",
"description": ""
},
{
"id": "7",
"name": "Task Three",
"description": ""
}
]
}
]
}
boardstore.js
import { defineStore } from "pinia";
import boardData from '~/data/board.json';
export const useBoardStore = defineStore('boardStore', ()=> {});
import { defineStore } from "pinia";
import boardData from '~/data/board.json';
export const useBoardStore = defineStore('boardStore', ()=> {
const board = ref(boardData)
return {
board
}
});
boardStore.js
import { defineStore } from "pinia";
import boardData from '~/data/board.json';
export const useBoardStore = defineStore('boardStore', ()=> {
const board = ref(boardData)
return {
board
}
});
in app.vue
<script setup >
import { useBoardStore } from '../stores/boardStore';
const boardStore = useBoardStore();
</script>
<template>
<h1> build vue </h1>
<pre> {{boardStore.board}} </pre>
</template>
nuxt ui library
tailwind css they ae using
npm install @nuxt/ui
nuxtconfig.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@nuxt/ui','@pinia/nuxt'],
})
nuxt config ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@nuxt/ui','@pinia/nuxt'],
colorMode:{
preference:'light'
}
})
building the ui:
assets/css/vello.css
.board {
@apply flex flex-row items-start;
}
.board-wrapper {
@apply p-4 h-full overflow-auto;
}
.column {
@apply flex-1 p-4 mr-4 rounded bg-gray-200;
min-width: 350px;
}
.column-header {
@apply flex items-center justify-between mb-3 font-bold;
}
.task-bg {
@apply absolute inset-0;
background: rgba(0, 0, 0, 0.75);
}
.task-view {
@apply flex flex-col flex-grow items-start justify-between px-4;
}
.task-wrapper {
@apply max-w-2xl bg-gray-200 m-32 mx-auto py-4 rounded;
}
install the tail wind extension
in nuxt.config.json
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
css:['~/assets/css/vello.css'],
devtools: { enabled: true },
modules: ['@nuxt/ui','@pinia/nuxt'],
colorMode:{
preference:'light'
}
})
app.vue
<template>
<Html class="bg-emerald-500">
<div>
<NuxtPage />
</div>
</Html>
</template>
index.vue
<script setup >
import { useBoardStore } from '../stores/boardStore';
const boardStore = useBoardStore();
</script>
<template>
<h1> {{boardStore.board.name}} </h1>
<div v-for="column in boardStore.board.columns" :key="column.name" class="column">
<h2> {{column.name}} </h2>
<ul>
<li v-for="task in column.tasks" :key="task.id">
<strong>{{task.name}}</strong>
<p>{{task.description}} </p>
</li>
</ul>
</div>
</template>
and
index.vue modified to
<script setup >
import { useBoardStore } from '../stores/boardStore';
const boardStore = useBoardStore();
</script>
<template>
<h1> {{boardStore.board.name}} </h1>
<div class="board-wrapper">
<main class="board">
<UContainer
v-for="column in boardStore.board.columns" :key="column.name" class="column"
>
<h2> {{column.name}} </h2>
<ul>
<li v-for="task in column.tasks" :key="task.id">
<strong>{{task.name}}</strong>
<p>{{task.description}} </p>
</li>
</ul>
</UContainer>
</main>
</div>
</template>
s
<input type="text" placeholder="Its A app">
s Uinput
<script setup >
import { useBoardStore } from '../stores/boardStore';
const boardStore = useBoardStore();
</script>
<template>
<h1> {{boardStore.board.name}} </h1>
<div class="board-wrapper">
<main class="board">
<UContainer
v-for="column in boardStore.board.columns" :key="column.name" class="column"
>
<h2> {{column.name}} </h2>
<ul>
<li v-for="task in column.tasks" :key="task.id">
<UCard class="mb-2">
<strong>{{task.name}}</strong>
<p>{{task.description}} </p>
</UCard>
</li>
</ul>
</UContainer>
<UContainer>
<UInput color="primary" variant="outline" placeholder="Search..." ICON="i-heroicons-plus-circle-solid" />
</UContainer>
</main>
</div>
</template>
https://ui.nuxt.com/getting-started/installation
https://ui.nuxt.com/components/input
create new column
in index.vue
const newColumnName= ref('');
function addColumn(){
boardStore.addColumn(newColumnName.value);
newColumnName.value='';
}
<UContainer class="column">
<UInput
v-model="newColumnName"
type="text"
placeholder="Create new column"
icon="i-heroicons-plus-circle-solid"
/>
</UContainer>
npm i @vueuse/core @vueuse/nuxt
and configure
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
css:['~/assets/css/vello.css'],
devtools: { enabled: true },
modules: ['@nuxt/ui','@pinia/nuxt','@vueuse/nuxt'],
colorMode:{
preference:'light'
}
})
index.vue
<script setup >
import { useBoardStore } from '../stores/boardStore';
const boardStore = useBoardStore();
const newColumnName = ref('')
function addColumn() {
boardStore.addColumn(newColumnName.value)
newColumnName.value = ''
}
</script>
<template>
<h1> {{boardStore.board.name}} </h1>
<div class="board-wrapper">
<main class="board">
<UContainer
v-for="column in boardStore.board.columns" :key="column.name" class="column"
>
<h2> {{column.name}} </h2>
<ul>
<li v-for="task in column.tasks" :key="task.id">
<UCard class="mb-2">
<strong>{{task.name}}</strong>
<p>{{task.description}} </p>
</UCard>
</li>
</ul>
</UContainer>
<UContainer class="column">
<UInput
v-model="newColumnName"
type="text"
placeholder="Create new column"
icon="i-heroicons-plus-circle-solid"
@keyup.enter="addColumn"
/>
</UContainer>
</main>
</div>
</template>
boardstore.js
import { defineStore } from "pinia";
import boardData from '~/data/board.json';
export const useBoardStore = defineStore('boardStore', ()=> {
const board = ref(boardData)
function addColumn(columnName) {
console.log('s',columnName);
board.value.columns.push({
name: columnName,
tasks: []
})
}
return {
board,
addColumn
}
});
to store in local storeage modify like below
now storing into localstoreage and
click on new it creates new button
ssr:false
now header name edit deltee
<div class="column-header">
<div>
<UInput v-if="editNameState" type="text" v-model="column.name" />
<h2 v-else class="mb-4"> {{column.name}} </h2>
</div>
<UButton icon="i-heroicons-pencil-square" class="mr-2"/>
<UButton icon="i-heroicons-trash" color="red"/>
</div>
=
delete the column and edit th ecolumn
const editNameState =ref(false);
function addColumn() {
boardStore.addColumn(newColumnName.value)
newColumnName.value = ''
}
function deleteColumn(columnIndex) {
boardStore.deleteColumn(columnIndex)
}
</script>
=
import { defineStore } from "pinia";
import boardData from '~/data/board.json';
import { useStorage } from '@vueuse/core';
export const useBoardStore = defineStore('boardStore', ()=> {
const board = useStorage('board', boardData)
function addColumn(columnName) {
console.log('s',columnName);
board.value.columns.push({
name: columnName,
tasks: []
})
}
function deleteColumn(columnIndex) {
board.value.columns.splice(columnIndex, 1)
}
return {
board,
addColumn,
deleteColumn,
}
});
=
<UContainer
v-for="(column,columnIndex) in boardStore.board.columns" :key="column.name" class="column"
>
<div class="column-header mb-4">
<div>
<UInput v-if="editNameState" type="text" v-model="column.name" />
<h2 v-else class="mb-4"> {{column.name}} </h2>
</div>
<UButton icon="i-heroicons-pencil-square" class="mr-2"
@click="editNameState = !editNameState"
/>
<UButton icon="i-heroicons-trash" color="red" @click="deleteColumn(columnIndex)" />
</div>
<ul>
<li v-for="task in column.tasks" :key="task.id">
<UCard class="mb-2">
<strong>{{task.name}}</strong>
<p>{{task.description}} </p>
</UCard>
</li>
</ul>
</UContainer>
index.vue
<script setup >
import { useBoardStore } from '../stores/boardStore';
const boardStore = useBoardStore();
const newColumnName = ref('')
const editNameState =ref(false);
function addColumn() {
boardStore.addColumn(newColumnName.value)
newColumnName.value = ''
}
function deleteColumn(columnIndex) {
boardStore.deleteColumn(columnIndex,1)
}
</script>
<template>
<h1> {{boardStore.board.name}} </h1>
<div class="board-wrapper">
<main class="board">
<UContainer
v-for="(column,columnIndex) in boardStore.board.columns" :key="column.name" class="column"
>
<div class="column-header mb-4">
<div>
<UInput v-if="editNameState" type="text" v-model="column.name" />
<h2 v-else class="mb-4"> {{column.name}} </h2>
</div>
<UButton icon="i-heroicons-pencil-square" class="mr-2"
@click="editNameState = !editNameState"
/>
<UButton icon="i-heroicons-trash" color="red" @click="deleteColumn(columnIndex)" />
</div>
<ul>
<li v-for="task in column.tasks" :key="task.id">
<UCard class="mb-2">
<strong>{{task.name}}</strong>
<p>{{task.description}} </p>
</UCard>
</li>
</ul>
</UContainer>
<UContainer class="column">
<UInput
v-model="newColumnName"
type="text"
placeholder="Create new column"
icon="i-heroicons-plus-circle-solid"
@keyup.enter="addColumn"
/>
</UContainer>
</main>
</div>
</template>
click on edit
its editing all columns
<script setup>
import { useBoardStore } from '../stores/boardStore'
defineProps({
column: {
type: Object,
required: true
},
columnIndex: {
type: Number,
required: true
}
})
const boardStore = useBoardStore()
const editNameState = ref(false)
function deleteColumn(columnIndex) {
boardStore.deleteColumn(columnIndex)
}
</script>
<template>
<UContainer class="column">
<div class="column-header mb-4">
<div>
<UInput v-if="editNameState" type="text" v-model="column.name" />
<h2 v-else>{{ column.name }}</h2>
</div>
<div>
<UButton
icon="i-heroicons-pencil-square"
class="mr-2"
@click="editNameState = !editNameState"
/>
<UButton
icon="i-heroicons-trash"
color="red"
@click="deleteColumn(columnIndex)"
/>
</div>
</div>
<ul>
<li v-for="task in column.tasks" :key="task.id">
<UCard class="mb-4">
<strong>{{ task.name }}</strong>
<p>{{ task.description }}</p>
</UCard>
</li>
</ul>
</UContainer>
</template>
we are caling using v-for
<BoardColumn
v-for="(column, columnIndex) in boardStore.board.columns"
:key="column.id"
:column="column"
:columnIndex="columnIndex"
/>
total index.vue
<script setup>
import { useBoardStore } from '../stores/boardStore'
const boardStore = useBoardStore()
const newColumnName = ref('')
function addColumn() {
boardStore.addColumn(newColumnName.value)
newColumnName.value = ''
}
</script>
<template>
<div class="board-wrapper">
<main class="board">
<BoardColumn
v-for="(column, columnIndex) in boardStore.board.columns"
:key="column.id"
:column="column"
:columnIndex="columnIndex"
/>
<UContainer class="column">
<UInput
v-model="newColumnName"
type="text"
placeholder="Create new column"
icon="i-heroicons-plus-circle-solid"
@keyup.enter="addColumn"
/>
</UContainer>
</main>
</div>
</template>
we cahnged :key="column.id"
because this it cahnged that key only
==
Display Task ROUTE IN MODEL
TASKS/[ID].VUE
WRITE below code
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
</script>
<template>
<h1> Page: {{ route.params.id }} </h1>
</template>
route is pages structure
http://localhost:3000/tasks/vamsi
get the data
<UCard class="mb-4" @click="goToTask(task.id)">
<strong>{{ task.name }}</strong>
<p>{{ task.description }}</p>
</UCard>
function goToTask(taskId) {
router.push(`/tasks/${taskId}`)
}
get the respective data from store
<script setup>
import { useBoardStore } from '~/stores/boardStore'
const boardStore = useBoardStore()
const route = useRoute()
const task = computed(() => {
return boardStore.getTask(route.params.id)
})
</script>
<template>
<div class="task-wrapper">
<div class="task-view">
<h1>{{ task.name }}</h1>
<p>{{ task.description }}</p>
</div>
</div>
</template>
store
const getTask = computed(() => {
return taskId => {
for (const column of board.value.columns) {
const task = column.tasks.find(task => task.id === taskId)
if (task) return task
}
}
})
finally
boardstore.js
import { defineStore } from 'pinia'
import { useStorage } from '@vueuse/core'
import boardData from '~/data/board.json'
export const useBoardStore = defineStore('boardStore', () => {
const board = useStorage('board', boardData)
const getTask = computed(() => {
return taskId => {
for (const column of board.value.columns) {
const task = column.tasks.find(task => task.id === taskId)
if (task) return task
}
}
})
function addColumn(columnName) {
board.value.columns.push({
name: columnName,
tasks: []
})
}
function deleteColumn(columnIndex) {
board.value.columns.splice(columnIndex, 1)
}
return {
/* State */
board,
/* Getters */
getTask,
/* Actions*/
addColumn,
deleteColumn
}
})
boardcolumn.vue
<script setup>
import { routerKey } from 'vue-router'
import { useBoardStore } from '../stores/boardStore'
defineProps({
column: {
type: Object,
required: true
},
columnIndex: {
type: Number,
required: true
}
})
const boardStore = useBoardStore()
const router = useRouter()
const editNameState = ref(false)
function deleteColumn(columnIndex) {
boardStore.deleteColumn(columnIndex)
}
function goToTask(taskId) {
router.push(`/tasks/${taskId}`)
}
</script>
<template>
<UContainer class="column">
<div class="column-header mb-4">
<div>
<UInput v-if="editNameState" type="text" v-model="column.name" />
<h2 v-else>{{ column.name }}</h2>
</div>
<div>
<UButton
icon="i-heroicons-pencil-square"
class="mr-2"
@click="editNameState = !editNameState"
/>
<UButton
icon="i-heroicons-trash"
color="red"
@click="deleteColumn(columnIndex)"
/>
</div>
</div>
<ul>
<li v-for="task in column.tasks" :key="task.id">
<UCard class="mb-4" @click="goToTask(task.id)">
<strong>{{ task.name }}</strong>
<p>{{ task.description }}</p>
</UCard>
</li>
</ul>
</UContainer>
</template>
id.vue
<script setup>
import { useBoardStore } from '~/stores/boardStore'
const boardStore = useBoardStore()
const route = useRoute()
const task = computed(() => {
return boardStore.getTask(route.params.id)
})
</script>
<template>
<div class="task-wrapper">
<div class="task-view">
<h1>{{ task.name }}</h1>
<p>{{ task.description }}</p>
</div>
</div>
</template>
instead of opening task in new tab
open in modal
click on that
show modal
show that route in url
open another page in modal use nuxt page
<div class="task-bg">
<NuxtPage />
</div>
=
[
No comments:
Post a Comment