build a trello clone with nuxt 3

 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>

crud for cloumns
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




nuxt.config.ts
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

create new component and copy above code

<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

clikc on particul;ar task 
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

React -1 ( react with ravenndra kanchi)

 react with tool chanis 45 to 50 sessions core  after 50 sessions -> some tooll chains == react with type script mern stackaplication in ...