├── public ├── robots.txt └── favicon.ico ├── server ├── tsconfig.json └── api │ ├── notes.js │ ├── notes.post.js │ ├── notes │ ├── [id].delete.js │ └── [id].patch.js │ ├── login.post.js │ └── user.post.js ├── assets └── main.css ├── tsconfig.json ├── prisma ├── migrations │ ├── migration_lock.toml │ ├── 20241124173020_add_salt │ │ └── migration.sql │ ├── 20241124160537_init │ │ └── migration.sql │ ├── 20241204132113_add_user_id_to_notes │ │ └── migration.sql │ └── 20241204024408_add_note_table │ │ └── migration.sql └── schema.prisma ├── plugins └── jwt.server.js ├── .gitignore ├── tailwind.config.js ├── app.vue ├── lib └── prisma.ts ├── middleware └── auth.js ├── package.json ├── nuxt.config.ts ├── README.md ├── components ├── ArrowRight.vue ├── PencilIcon.vue ├── TrashIcon.vue └── Logo.vue └── pages ├── login.vue ├── register.vue └── index.vue /public/robots.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /assets/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/notenest/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "mysql" -------------------------------------------------------------------------------- /plugins/jwt.server.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | export default defineNuxtPlugin((nuxtApp) => { 4 | return { 5 | provide: { 6 | verifyJwtToken: (token, secret, options) => { 7 | return jwt.verify(token, secret, options) 8 | }, 9 | }, 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /prisma/migrations/20241124173020_add_salt/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `salt` to the `User` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `User` ADD COLUMN `salt` VARCHAR(191) NOT NULL; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | -------------------------------------------------------------------------------- /prisma/migrations/20241124160537_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE `User` ( 3 | `id` INTEGER NOT NULL AUTO_INCREMENT, 4 | `email` VARCHAR(191) NOT NULL, 5 | `password` VARCHAR(191) NOT NULL, 6 | 7 | UNIQUE INDEX `User_email_key`(`email`), 8 | PRIMARY KEY (`id`) 9 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 10 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | './components/**/*.{js,vue,ts}', 5 | './layouts/**/*.vue', 6 | './pages/**/*.vue', 7 | './plugins/**/*.{js,ts}', 8 | './app.vue', 9 | './error.vue', 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [], 15 | } 16 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | 3 | const prismaClientSingleton = () => { 4 | return new PrismaClient() 5 | } 6 | 7 | declare const globalThis: { 8 | prismaGlobal: ReturnType; 9 | } & typeof global; 10 | 11 | const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() 12 | 13 | export default prisma 14 | 15 | if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma 16 | -------------------------------------------------------------------------------- /prisma/migrations/20241204132113_add_user_id_to_notes/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `userId` to the `Note` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `Note` ADD COLUMN `userId` INTEGER NOT NULL; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE `Note` ADD CONSTRAINT `Note_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /prisma/migrations/20241204024408_add_note_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `User` ADD COLUMN `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 3 | ADD COLUMN `updatedAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3); 4 | 5 | -- CreateTable 6 | CREATE TABLE `Note` ( 7 | `id` INTEGER NOT NULL AUTO_INCREMENT, 8 | `text` TEXT NOT NULL, 9 | `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 10 | `updatedAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 11 | 12 | PRIMARY KEY (`id`) 13 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 14 | -------------------------------------------------------------------------------- /middleware/auth.js: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware(async (event) => { 2 | if (process.client) return 3 | 4 | const { $verifyJwtToken } = useNuxtApp() 5 | 6 | console.log('middleware fired') 7 | const jwt = useCookie('NoteNestJWT') 8 | console.log(jwt.value) 9 | 10 | if (!jwt.value) { 11 | return navigateTo('/register') 12 | } 13 | 14 | try { 15 | await $verifyJwtToken(jwt.value, process.env.JWT_SECRET) 16 | } catch (error) { 17 | console.log(error) 18 | return navigateTo('/register') 19 | } 20 | }) 21 | 22 | // jsonwebtoken = klfjdsalkfjklsdajkl4jfkslkdfjl.fadsjklfjsdklfjskla.asdfsafsdasdfsafsadjksldfjsdkl 23 | -------------------------------------------------------------------------------- /server/api/notes.js: -------------------------------------------------------------------------------- 1 | // /api/notes return all the notes 2 | import jwt from 'jsonwebtoken' 3 | export default defineEventHandler(async (event) => { 4 | try { 5 | const cookies = parseCookies(event) 6 | const token = cookies.NoteNestJWT 7 | 8 | if (!token) { 9 | throw createError({ 10 | statusCode: 401, 11 | statusMessage: 'Not authorized to access notes', 12 | }) 13 | } 14 | 15 | const decodedToken = await jwt.verify(token, process.env.JWT_SECRET) 16 | 17 | const notes = await prisma.note.findMany({ 18 | where: { 19 | userId: decodedToken.id, 20 | }, 21 | }) 22 | 23 | return notes 24 | } catch (err) { 25 | console.log(err) 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /server/api/notes.post.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | export default defineEventHandler(async (event) => { 4 | try { 5 | const cookies = parseCookies(event) 6 | const token = cookies.NoteNestJWT 7 | 8 | if (!token) { 9 | throw createError({ 10 | statusCode: 401, 11 | statusMessage: 'Not authorized to update', 12 | }) 13 | } 14 | 15 | const decodedToken = await jwt.verify(token, process.env.JWT_SECRET) 16 | 17 | const newNote = await prisma.note.create({ 18 | data: { 19 | text: '', 20 | userId: decodedToken.id, 21 | }, 22 | }) 23 | 24 | return newNote 25 | } catch (err) { 26 | throw createError({ 27 | statusCode: 500, 28 | statusMessage: 'Could not verify jwt', 29 | }) 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "nuxt build", 7 | "dev": "nuxt dev", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview", 10 | "postinstall": "nuxt prepare" 11 | }, 12 | "dependencies": { 13 | "@prisma/nuxt": "^0.1.0", 14 | "@vueuse/core": "^12.0.0", 15 | "@vueuse/nuxt": "^12.0.0", 16 | "bcryptjs": "^2.4.3", 17 | "jsonwebtoken": "^9.0.2", 18 | "nuxt": "^3.14.1592", 19 | "sweetalert2": "^11.14.5", 20 | "validator": "^13.12.0", 21 | "vue": "latest", 22 | "vue-router": "latest" 23 | }, 24 | "devDependencies": { 25 | "@prisma/client": "^5.22.0", 26 | "autoprefixer": "^10.4.20", 27 | "postcss": "^8.4.49", 28 | "prisma": "^5.22.0", 29 | "tailwindcss": "^3.4.15" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | app: { 4 | head: { 5 | link: [ 6 | { 7 | rel: 'preconnect', 8 | href: 'https://fonts.googleapis.com"', 9 | }, 10 | { 11 | rel: 'preconnect', 12 | href: 'https://fonts.gstatic.com', 13 | crossorigin: 'anonymous', 14 | }, 15 | { 16 | rel: 'stylesheet', 17 | href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Playfair:ital,opsz,wght@0,5..1200,300..900;1,5..1200,300..900&display=swap', 18 | }, 19 | ], 20 | }, 21 | }, 22 | 23 | compatibilityDate: '2024-11-01', 24 | devtools: { enabled: true }, 25 | css: ['~/assets/main.css'], 26 | 27 | postcss: { 28 | plugins: { 29 | tailwindcss: {}, 30 | autoprefixer: {}, 31 | }, 32 | }, 33 | 34 | modules: ['@prisma/nuxt', '@vueuse/nuxt'], 35 | }) 36 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? 5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init 6 | 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | datasource db { 12 | provider = "mysql" 13 | url = env("DATABASE_URL") 14 | } 15 | 16 | model User { 17 | id Int @id @default(autoincrement()) 18 | email String @unique 19 | password String 20 | salt String 21 | createdAt DateTime @default(now()) 22 | updatedAt DateTime @default(now()) @updatedAt 23 | notes Note[] 24 | } 25 | 26 | model Note { 27 | id Int @id @default(autoincrement()) 28 | userId Int 29 | text String @db.Text 30 | createdAt DateTime @default(now()) 31 | updatedAt DateTime @default(now()) @updatedAt 32 | 33 | user User @relation(fields: [userId], references: [id]) 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt Minimal Starter 2 | 3 | Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. 4 | 5 | ## Setup 6 | 7 | Make sure to install dependencies: 8 | 9 | ```bash 10 | # npm 11 | npm install 12 | 13 | # pnpm 14 | pnpm install 15 | 16 | # yarn 17 | yarn install 18 | 19 | # bun 20 | bun install 21 | ``` 22 | 23 | ## Development Server 24 | 25 | Start the development server on `http://localhost:3000`: 26 | 27 | ```bash 28 | # npm 29 | npm run dev 30 | 31 | # pnpm 32 | pnpm dev 33 | 34 | # yarn 35 | yarn dev 36 | 37 | # bun 38 | bun run dev 39 | ``` 40 | 41 | ## Production 42 | 43 | Build the application for production: 44 | 45 | ```bash 46 | # npm 47 | npm run build 48 | 49 | # pnpm 50 | pnpm build 51 | 52 | # yarn 53 | yarn build 54 | 55 | # bun 56 | bun run build 57 | ``` 58 | 59 | Locally preview production build: 60 | 61 | ```bash 62 | # npm 63 | npm run preview 64 | 65 | # pnpm 66 | pnpm preview 67 | 68 | # yarn 69 | yarn preview 70 | 71 | # bun 72 | bun run preview 73 | ``` 74 | 75 | Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. 76 | -------------------------------------------------------------------------------- /server/api/notes/[id].delete.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | export default defineEventHandler(async (event) => { 4 | try { 5 | const id = await getRouterParam(event, 'id') 6 | 7 | const cookies = parseCookies(event) 8 | const token = cookies.NoteNestJWT 9 | 10 | if (!token) { 11 | throw createError({ 12 | statusCode: 401, 13 | statusMessage: 'Not authorized to update', 14 | }) 15 | } 16 | 17 | const decodedToken = await jwt.verify(token, process.env.JWT_SECRET) 18 | 19 | const noteTryingToDelete = await prisma.note.findUnique({ 20 | where: { 21 | id: Number(id), 22 | }, 23 | }) 24 | 25 | if (!noteTryingToDelete) { 26 | throw createError({ 27 | statusCode: 401, 28 | statusMessage: 'Note does not exist', 29 | }) 30 | } 31 | 32 | if (noteTryingToDelete.userId !== decodedToken.id) { 33 | throw createError({ 34 | statusCode: 401, 35 | statusMessage: 'Does not have permission to update note', 36 | }) 37 | } 38 | 39 | await prisma.note.delete({ 40 | where: { 41 | id: Number(id), 42 | }, 43 | }) 44 | console.log(body) 45 | } catch (err) { 46 | console.log(err) 47 | } 48 | }) 49 | -------------------------------------------------------------------------------- /components/ArrowRight.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /server/api/notes/[id].patch.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | export default defineEventHandler(async (event) => { 4 | try { 5 | const body = await readBody(event) 6 | const id = await getRouterParam(event, 'id') 7 | 8 | const cookies = parseCookies(event) 9 | const token = cookies.NoteNestJWT 10 | 11 | if (!token) { 12 | throw createError({ 13 | statusCode: 401, 14 | statusMessage: 'Not authorized to update', 15 | }) 16 | } 17 | 18 | const decodedToken = await jwt.verify(token, process.env.JWT_SECRET) 19 | 20 | const noteTryingToUpdate = await prisma.note.findUnique({ 21 | where: { 22 | id: Number(id), 23 | }, 24 | }) 25 | 26 | if (!noteTryingToUpdate) { 27 | throw createError({ 28 | statusCode: 401, 29 | statusMessage: 'Note does not exist', 30 | }) 31 | } 32 | 33 | if (noteTryingToUpdate.userId !== decodedToken.id) { 34 | throw createError({ 35 | statusCode: 401, 36 | statusMessage: 'Does not have permission to update note', 37 | }) 38 | } 39 | 40 | console.log(id) 41 | 42 | await prisma.note.update({ 43 | where: { 44 | id: Number(id), 45 | }, 46 | data: { 47 | text: body.updatedNote, 48 | }, 49 | }) 50 | console.log(body) 51 | } catch (err) { 52 | console.log(err) 53 | } 54 | }) 55 | -------------------------------------------------------------------------------- /components/PencilIcon.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/api/login.post.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs' 2 | import validator from 'validator' 3 | import jwt from 'jsonwebtoken' 4 | 5 | export default defineEventHandler(async (event) => { 6 | try { 7 | const body = await readBody(event) 8 | 9 | if (!validator.isEmail(body.email)) { 10 | throw createError({ 11 | statusCode: 400, 12 | message: 'Invalid email, please change.', 13 | }) 14 | } 15 | 16 | if ( 17 | !validator.isStrongPassword(body.password, { 18 | minLength: 8, 19 | minLowercase: 0, 20 | minUppercase: 0, 21 | minNumbers: 0, 22 | minSymbols: 0, 23 | }) 24 | ) { 25 | throw createError({ 26 | statusCode: 400, 27 | message: 'Password is not minimum 8 characters, please change.', 28 | }) 29 | } 30 | 31 | const user = await prisma.user.findUnique({ 32 | where: { 33 | email: body.email, 34 | }, 35 | }) 36 | 37 | const isValid = await bcrypt.compare(body.password, user.password) 38 | 39 | console.log('IS VALID:') 40 | console.log(isValid) 41 | 42 | if (!isValid) { 43 | throw createError({ 44 | statusCode: 400, 45 | message: 'Username or password is invalid.', 46 | }) 47 | } 48 | 49 | const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET) 50 | 51 | setCookie(event, 'NoteNestJWT', token) 52 | 53 | return { data: 'success!' } 54 | } catch (error) { 55 | console.log(error.code) 56 | 57 | if (error.code === 'P2002') { 58 | throw createError({ 59 | statusCode: 409, 60 | message: 'An email with this address already exists.', 61 | }) 62 | } 63 | 64 | throw error 65 | } 66 | }) 67 | 68 | // GET 69 | // POST 70 | // PATCH 71 | // PUT 72 | // DELETE 73 | -------------------------------------------------------------------------------- /server/api/user.post.js: -------------------------------------------------------------------------------- 1 | // /api/user POST 2 | 3 | // Hashing passwords 4 | // - Prevents PW from being stored in plaintext 5 | // - mypassword123 jnjvsadcjncuwinuiwebjksab,/#@$fasDFVCASDR$@# 6 | 7 | // Salts 8 | // - salt = string of random characters 9 | // - Typically added to the beginning of a user's PW 10 | // - mypassword123 becomes x#fSA#Amypassword123 11 | // - Used to prevent hackers from using precomputed hash tables to crack a PW 12 | // - Each user gets their own salt so even if two users have the same PW 13 | // their password's look completely different 14 | 15 | // Generate secret: 16 | // - node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" 17 | 18 | import bcrypt from 'bcryptjs' 19 | import validator from 'validator' 20 | import jwt from 'jsonwebtoken' 21 | 22 | export default defineEventHandler(async (event) => { 23 | try { 24 | const body = await readBody(event) 25 | 26 | if (!validator.isEmail(body.email)) { 27 | throw createError({ 28 | statusCode: 400, 29 | message: 'Invalid email, please change.', 30 | }) 31 | } 32 | 33 | if ( 34 | !validator.isStrongPassword(body.password, { 35 | minLength: 8, 36 | minLowercase: 0, 37 | minUppercase: 0, 38 | minNumbers: 0, 39 | minSymbols: 0, 40 | }) 41 | ) { 42 | throw createError({ 43 | statusCode: 400, 44 | message: 'Password is not minimum 8 characters, please change.', 45 | }) 46 | } 47 | 48 | const salt = await bcrypt.genSalt(10) 49 | const passwordHash = await bcrypt.hash(body.password, salt) 50 | 51 | // Sends to database 52 | const user = await prisma.user.create({ 53 | data: { 54 | email: body.email, 55 | password: passwordHash, 56 | salt: salt, 57 | }, 58 | }) 59 | 60 | const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET) 61 | 62 | setCookie(event, 'NoteNestJWT', token) 63 | 64 | return { data: 'success!' } 65 | } catch (error) { 66 | console.log(error.code) 67 | 68 | if (error.code === 'P2002') { 69 | throw createError({ 70 | statusCode: 409, 71 | message: 'An email with this address already exists.', 72 | }) 73 | } 74 | 75 | throw error 76 | } 77 | }) 78 | 79 | // GET 80 | // POST 81 | // PATCH 82 | // PUT 83 | // DELETE 84 | -------------------------------------------------------------------------------- /components/TrashIcon.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /pages/login.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Log in to your account 7 | 8 | Don't have an account? 9 | Sign Up 12 | for one. 13 | 14 | 15 | 16 | 17 | Email Address 20 | 26 | 27 | 28 | 29 | Password 32 | 38 | 39 | 40 | 41 | 42 | 45 | Log in 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 98 | -------------------------------------------------------------------------------- /pages/register.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sign up for a free account 8 | 9 | 10 | Already registered? 11 | Log in 14 | to your account 15 | 16 | 17 | 18 | 19 | Email Address 22 | 28 | 29 | 30 | 31 | Password 34 | 40 | 41 | 42 | 43 | 44 | 47 | Sign Up 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 99 | -------------------------------------------------------------------------------- /components/Logo.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Today 12 | 13 | 22 | 23 | {{ note.text.substring(0, 50) }} 24 | 25 | 26 | {{ 27 | new Date(note.updatedAt).toLocaleDateString() 28 | }} 29 | ... {{ note.text.substring(50, 100) }} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Yesterday 41 | 42 | 51 | 52 | {{ note.text.substring(0, 50) }} 53 | 54 | 55 | {{ 56 | new Date(note.updatedAt).toDateString() === 57 | new Date().toDateString() 58 | ? 'Today' 59 | : new Date(note.updatedAt).toLocaleDateString() 60 | }} 61 | ... {{ note.text.substring(50, 100) }} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Earlier 73 | 74 | 83 | 84 | {{ note.text.substring(0, 50) }} 85 | 86 | 87 | {{ 88 | new Date(note.updatedAt).toDateString() === 89 | new Date().toDateString() 90 | ? 'Today' 91 | : new Date(note.updatedAt).toLocaleDateString() 92 | }} 93 | ... {{ note.text.substring(50, 100) }} 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 110 | 111 | Create Note 112 | 113 | 114 | 115 | 119 | 120 | 121 | 122 | 123 | 124 | {{ new Date(selectedNote.updatedAt).toLocaleDateString() }} 125 | 126 | { 134 | debouncedFn() 135 | selectedNote.text = updatedNote 136 | } 137 | " 138 | > 139 | 140 | 141 | 142 | 146 | Logout 147 | 148 | 149 | 150 | 151 | 152 | 153 | 275 | --------------------------------------------------------------------------------
8 | Don't have an account? 9 | Sign Up 12 | for one. 13 |
10 | Already registered? 11 | Log in 14 | to your account 15 |
Today
Yesterday
Earlier
124 | {{ new Date(selectedNote.updatedAt).toLocaleDateString() }} 125 |