From b9f635e570b0d69a51d649553c836017e5b6d34c Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 29 Apr 2024 21:36:35 +0300 Subject: [PATCH 1/8] custom sign-in page --- pages/auth/signin.tsx | 105 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 pages/auth/signin.tsx diff --git a/pages/auth/signin.tsx b/pages/auth/signin.tsx new file mode 100644 index 0000000..0326411 --- /dev/null +++ b/pages/auth/signin.tsx @@ -0,0 +1,105 @@ +// pages/auth/signin.js +import { getCsrfToken, signIn } from 'next-auth/react'; +import React, { useState } from 'react'; +import { useRouter } from 'next/router'; +import Layout from '../../components/layout'; + +export default function SignIn({ csrfToken }) { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const router = useRouter(); + + const handleSubmit = async (e) => { + e.preventDefault(); + + // Perform client-side validation if needed + if (!email || !password) { + setError('All fields are required'); + return; + } + + // Clear any existing errors + setError(''); + + // Attempt to sign in + const result = await signIn('credentials', { + redirect: false, + username: email, + password, + callbackUrl: '/', + }); + + // Check if there was an error + if (result.error) { + setError(result.error); + } + + // Redirect to the home page or callbackUrl on success + if (result.ok && result.url) { + router.push(result.url); + } + }; + + return ( + +
+
+
+
+ {/* Button to sign in with Google */} + +
+
+
+ +
+ + setEmail(e.target.value)} + className="border p-2 w-full" + /> +
+
+ + setPassword(e.target.value)} + className="border p-2 w-full" + /> +
+ {error &&
{error}
} + + +
+
+
+
+
+
+ ); +} + +// This gets called on every request +export async function getServerSideProps(context) { + return { + props: { + csrfToken: await getCsrfToken(context), + }, + }; +} From 1e47ba669a2b6aa69a3d4aa8e18a0efd81cbb95d Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 29 Apr 2024 22:24:44 +0300 Subject: [PATCH 2/8] db support --- prisma/migrations/20240429133812_/migration.sql | 3 +++ prisma/schema.prisma | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 prisma/migrations/20240429133812_/migration.sql diff --git a/prisma/migrations/20240429133812_/migration.sql b/prisma/migrations/20240429133812_/migration.sql new file mode 100644 index 0000000..76f1dda --- /dev/null +++ b/prisma/migrations/20240429133812_/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE `User` +ADD COLUMN `passwordHashLocalAccount` VARCHAR(191) NULL; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 25ce5dd..03d8a40 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -278,13 +278,14 @@ model EventLog { //user auth and session management model User { - id String @id @default(cuid()) - name String? - email String? @unique - emailVerified DateTime? - image String? - accounts Account[] - sessions Session[] + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] + passwordHashLocalAccount String? // New field to store the hashed password // Optional relation to Publisher publisherId String? @unique From d77791391035e03b0ed699bd91b3c83cc81fdb40 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 30 Apr 2024 02:47:00 +0300 Subject: [PATCH 3/8] auth with local credentials --- package-lock.json | 276 +++++++++++++++++++++++++++++++- package.json | 3 +- pages/api/auth/[...nextauth].ts | 100 ++++++++---- 3 files changed, 345 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 28c3295..e6292bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,8 @@ "autoprefixer": "^10.4.17", "axios": "^1.6.7", "axios-jwt": "^4.0.2", + "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", "date-fns": "^3.3.1", "docx": "^8.5.0", "docx-templates": "^4.11.4", @@ -2861,6 +2863,69 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/@mui/base": { "version": "5.0.0-beta.36", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.36.tgz", @@ -4990,6 +5055,11 @@ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, "node_modules/archiver": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", @@ -5054,6 +5124,18 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -5396,6 +5478,19 @@ } ] }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -5404,6 +5499,11 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -5841,6 +5941,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -6068,6 +6176,14 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/color/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6197,6 +6313,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -6591,6 +6712,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depcheck": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-1.4.7.tgz", @@ -8020,6 +8146,28 @@ "node": ">=10" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8112,6 +8260,25 @@ "resolved": "https://registry.npmjs.org/gapi-script/-/gapi-script-1.2.0.tgz", "integrity": "sha512-NKTVKiIwFdkO1j1EzcrWu/Pz7gsl1GmBmgh+qhuV2Ytls04W/Eg5aiBL91SCiBM9lU0PMu7p1hTVxhh1rPT5Lw==" }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gaxios": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.3.0.tgz", @@ -8590,6 +8757,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -10543,6 +10715,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -10889,6 +11092,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/node-excel-export": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/node-excel-export/-/node-excel-export-1.4.4.tgz", @@ -11089,6 +11297,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -13784,6 +14006,17 @@ "inBundle": true, "license": "ISC" }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", @@ -15755,6 +15988,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", @@ -15873,8 +16111,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "optional": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/simple-swizzle": { "version": "0.2.2", @@ -16658,6 +16895,22 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -16673,6 +16926,17 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -17918,6 +18182,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/winston": { "version": "3.13.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", diff --git a/package.json b/package.json index f952b7d..89803af 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "autoprefixer": "^10.4.17", "axios": "^1.6.7", "axios-jwt": "^4.0.2", + "bcrypt": "^5.1.1", "date-fns": "^3.3.1", "docx": "^8.5.0", "docx-templates": "^4.11.4", @@ -115,4 +116,4 @@ "depcheck": "^1.4.7", "prisma": "^5.13.0" } -} +} \ No newline at end of file diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts index 2db98e9..84b4bee 100644 --- a/pages/api/auth/[...nextauth].ts +++ b/pages/api/auth/[...nextauth].ts @@ -8,6 +8,7 @@ import AppleProvider from "next-auth/providers/apple" import EmailProvider from "next-auth/providers/email" import CredentialsProvider from "next-auth/providers/credentials" import { PrismaAdapter } from "@auth/prisma-adapter" +import bcrypt from "bcrypt" //microsoft import AzureADProvider from "next-auth/providers/azure-ad"; @@ -16,6 +17,7 @@ import AzureADProvider from "next-auth/providers/azure-ad"; const common = require("../../../src/helpers/common"); import { isLoggedIn, setAuthTokens, clearAuthTokens, getAccessToken, getRefreshToken } from 'axios-jwt' +import { create } from "domain" console.log("appleID:", process.env.APPLE_APP_ID); @@ -52,6 +54,7 @@ export const authOptions: NextAuthOptions = { // tenantId: process.env.AZURE_AD_TENANT_ID, // }), CredentialsProvider({ + id: 'credentials', // The name to display on the sign in form (e.g. 'Sign in with...') name: 'Credentials', credentials: { @@ -80,17 +83,45 @@ export const authOptions: NextAuthOptions = { { id: "3", name: "popov", email: "popov@example.com", password: "popov123", role: "ADMIN" } ]; - // Check if a user with the given username and password exists const user = users.find(user => user.name === credentials.username && user.password === credentials.password ); - // If a matching user is found, return the user data, otherwise return null if (user) { - return user; //{ id: user.id, name: user.name, email: user.email }; + return user; } + else { + const prisma = common.getPrismaClient(); + const user = await prisma.user.findUnique({ where: { email: credentials.username } }); + if (user) { + const match = await bcrypt.compare(credentials?.password, user.passwordHashLocalAccount); + if (match) { + console.log("User authenticated successfully."); + //create access token + user.accessToken = await getAccessToken(); - return null; + return user; + } + else { + console.log("Password mismatch."); + throw new Error('невалидна парола'); + } + } + else { + throw new Error("Не можем да намерим твоя имейл '" + credentials?.username + "' в участниците в ССОМ. Моля свържи се с нас за да те регистрираме ако искаш да ползваш този имейл."); + // console.log("Creating new user in the database..."); + // const passHash = await bcrypt.hash(credentials.password, 10); + // const newUser = await prisma.user.create({ + // data: { + // name: credentials.username, + // email: credentials.username, + // passwordHashLocalAccount: passHash + // } + // }); + // console.log("New user created in the database."); + // return newUser; + } + } } }) /* @@ -132,35 +163,35 @@ export const authOptions: NextAuthOptions = { var prisma = common.getPrismaClient(); console.log("[nextauth] signIn:", account.provider, user.email) - if (account.provider === 'google') { - try { - // Check user in your database and assign roles - const dbUser = await prisma.publisher.findUnique({ - where: { email: user.email } + //if (account.provider === 'google' ) { + try { + // Check user in your database and assign roles + const dbUser = await prisma.publisher.findUnique({ + where: { email: user.email } + }); + + if (dbUser) { + // Assign roles from your database to the session + user.role = dbUser.role; + user.id = dbUser.id; + //user.permissions = dbUser.permissions; + const session = { ...user }; + + await prisma.publisher.update({ + where: { id: dbUser.id }, + data: { lastLogin: new Date() } }); - - if (dbUser) { - // Assign roles from your database to the session - user.role = dbUser.role; - user.id = dbUser.id; - //user.permissions = dbUser.permissions; - const session = { ...user }; - - await prisma.publisher.update({ - where: { id: dbUser.id }, - data: { lastLogin: new Date() } - }); - return true; // Sign-in successful - } else { - // Optionally create a new user in your DB - // Or return false to deny access - //Let's customize the error message to give a better user experience - throw new Error(`Твоят имейл '${user.email}' не е регистриран в системата. Моля свържи се с нас за да те регистрираме ако искаш да ползваш този имейл.`); - } - } catch (e) { - console.log(e); + return true; // Sign-in successful + } else { + // Optionally create a new user in your DB + // Or return false to deny access + //Let's customize the error message to give a better user experience + throw new Error(`Твоят имейл '${user.email}' не е регистриран в системата. Моля свържи се с нас за да те регистрираме ако искаш да ползваш този имейл.`); } + } catch (e) { + console.log(e); } + //} return true; // Allow other providers or default behavior }, @@ -207,6 +238,13 @@ export const authOptions: NextAuthOptions = { }; }, }, + pages: { + signIn: "/auth/signin", + signOut: "/auth/signout", + error: "/message", // Error code passed in query string as ?error= + verifyRequest: "/auth/verify-request", // (used for check email message) + newUser: null // If set, new users will be directed here on first sign in + }, } -export default NextAuth(authOptions) +export default NextAuth(authOptions) \ No newline at end of file From aa766f4e1ed63869da340356352a94b44407f51e Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 30 Apr 2024 02:49:40 +0300 Subject: [PATCH 4/8] password reset implementation; custom signin form --- pages/api/email.ts | 81 ++++++++++- pages/auth/reset-password.tsx | 135 ++++++++++++++++++ pages/auth/signin.tsx | 87 +++++------ .../migration.sql | 5 + prisma/schema.prisma | 3 + src/templates/emails/resetPass.hbs | 22 +++ 6 files changed, 290 insertions(+), 43 deletions(-) create mode 100644 pages/auth/reset-password.tsx create mode 100644 prisma/migrations/20240429233926_add_eventlog_types/migration.sql create mode 100644 src/templates/emails/resetPass.hbs diff --git a/pages/api/email.ts b/pages/api/email.ts index 68fcb22..0a0cfb4 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -34,7 +34,8 @@ export default async function handler(req, res) { // Retrieve and validate the JWT token //response is a special action that does not require a token - if (action == "email_response") { + //PUBLIC + if (action == "email_response" || action == "account") { switch (emailaction) { case "coverMeAccept": //validate shiftId and assignmentId @@ -201,6 +202,83 @@ export default async function handler(req, res) { }); break; + + case "resetPassword": + // Send password reset form to the user + //parse the request body + + let email = req.body.email || req.query.email; + let actualUser = await prisma.publisher.findUnique({ + where: { + email: email + } + }); + if (!actualUser) { + return res.status(200).json({ message: "Няма потребител с този имейл" }); + } + else { + let requestGuid = req.query.guid; + if (!requestGuid) { + console.log("User: " + email + " requested a password reset"); + let requestGuid = uuidv4(); + //save the request in the database as EventLog + let eventLog = await prisma.eventLog.create({ + data: { + date: new Date(), + publisher: { connect: { id: actualUser.id } }, + type: EventLogType.PasswordResetRequested, + content: JSON.stringify({ guid: requestGuid }) + } + }); + logger.info("User: " + email + " requested a password reset. EventLogId: " + eventLog.id + ""); + + let model = { + email: email, + firstName: actualUser.firstName, + lastName: actualUser.lastName, + resetUrl: process.env.NEXTAUTH_URL + "/api/email?action=email_response&emailaction=resetPassword&guid=" + requestGuid + "&email=" + email, + sentDate: common.getDateFormated(new Date()) + }; + emailHelper.SendEmailHandlebars(to, "resetPassword", model); + res.status(200).json({ message: "Password reset request sent" }); + } + else { + //1. validate the guid + let eventLog = await prisma.eventLog.findFirst({ + where: {//can we query "{ guid: requestGuid }"? + type: EventLogType.PasswordResetRequested, + publisherId: actualUser.id, + date: { + gt: new Date(new Date().getTime() - 24 * 60 * 60 * 1000) //24 hours + } + } + }); + + if (!eventLog) { + return res.status(400).json({ message: "Invalid or expired password reset request" }); + } + else { + let eventLog = await prisma.eventLog.update({ + where: { + id: parseInt(requestGuid) + }, + data: { + type: EventLogType.PasswordResetEmailConfirmed + } + }); + //2. redirect to the password reset page + const messagePageUrl = `/auth/reset-password?email=${email}&resetToken=${requestGuid}`; + res.redirect(messagePageUrl); + } + + //2.login the user + + //3. redirect to the password reset page + } + + } + break; + } // //send email response to the user // const emailResponse = await common.sendEmail(user.email, "Email Action Processed", @@ -220,6 +298,7 @@ export default async function handler(req, res) { } }); + //PRIVATE ACTIONS switch (action) { case "sendCoverMeRequestByEmail": // Send CoverMe request to the users diff --git a/pages/auth/reset-password.tsx b/pages/auth/reset-password.tsx new file mode 100644 index 0000000..82f8a4a --- /dev/null +++ b/pages/auth/reset-password.tsx @@ -0,0 +1,135 @@ +import { use, useEffect, useState } from 'react'; +import Layout from '../../components/layout'; +import axiosInstance from "../../src/axiosSecure"; +import common from '../../src/helpers/common'; +import { EventLogType } from '@prisma/client'; +import { useRouter } from "next/router"; + +export default function ResetPassword(req, res) { + const [email, setEmail] = useState(''); + const [message, setMessage] = useState(''); + const [resetToken, setResetToken] = useState(req.query?.resetToken || ''); + const [isConfirmed, setIsConfirmed] = useState(false); + const router = useRouter(); + + + useEffect(async () => { + if (resetToken) { + const prisma = common.getPrismaClient(); + let eventLog = await prisma.eventLog.findUnique({ + where: { + content: resetToken, + type: EventLogType.PasswordResetEmailConfirmed, + date: { + gt: new Date(new Date().getTime() - 24 * 60 * 60 * 1000) //24 hours + } + } + }); + if (eventLog) { + setIsConfirmed(true); + } + } + }, [resetToken]); + + const handleResetRequest = async (event) => { + event.preventDefault(); + // Call your email API endpoint here + try { + const response = await axiosInstance.post('/api/email?action=account&emailaction=resetPassword', { email }, + { headers: { 'Content-Type': 'application/json' } }); + if (response.data.message) { + setMessage(response.data.message); + } else { + if (response.ok) { + setMessage('Провери твоя имейл за инструкции как да промениш паролата си.'); + } else { + if (response.error) { + setMessage(response.error); + } + } + } + } catch (error) { + setMessage(error.message); + } + }; + + const setNewPassword = async (event) => { + event.preventDefault(); + + try { + const prisma = common.getPrismaClient(); + const user = await prisma.user.findUnique({ + where: { + email + } + }); + if (!user) { + throw new Error('Няма потребител с този имейл.'); + } + + const passHash = await crypto.hash(event.target.newPassword.value, 10); + await prisma.user.update({ + where: { + email + }, + data: { + passwordHashLocalAccount: passHash + } + }); + setMessage('Паролата беше успешно променена.'); + router.push('/auth/signin'); + + } catch (error) { + setMessage(error.message); + } + } + + + return ( + +
+
+

Променете паролата си

+
+ {!isConfirmed && +
+ + setEmail(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> +
} + + {isConfirmed && +
+ + setNewPassword(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> +
} +
+ + +
+ {message &&
{message}
} +
+
+
+
+ ); +} diff --git a/pages/auth/signin.tsx b/pages/auth/signin.tsx index 0326411..632f9ae 100644 --- a/pages/auth/signin.tsx +++ b/pages/auth/signin.tsx @@ -15,7 +15,7 @@ export default function SignIn({ csrfToken }) { // Perform client-side validation if needed if (!email || !password) { - setError('All fields are required'); + setError('Всички полета са задължителни'); return; } @@ -45,49 +45,52 @@ export default function SignIn({ csrfToken }) {
-
-
- {/* Button to sign in with Google */} - + {/* Add more buttons for other SSO providers here in similar style */}
-
-
- -
- - setEmail(e.target.value)} - className="border p-2 w-full" - /> -
-
- - setPassword(e.target.value)} - className="border p-2 w-full" - /> -
- {error &&
{error}
} - - -
-
+ + {/* Email and Password Form */} +
+ +
+ + setEmail(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> +
+
+ + setPassword(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> +
+ {error &&
{error}
} + + {/* */} +
diff --git a/prisma/migrations/20240429233926_add_eventlog_types/migration.sql b/prisma/migrations/20240429233926_add_eventlog_types/migration.sql new file mode 100644 index 0000000..bb47961 --- /dev/null +++ b/prisma/migrations/20240429233926_add_eventlog_types/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE `Eventlog` +MODIFY `type` ENUM( + 'AssignmentReplacementRequested', 'AssignmentReplacementAccepted', 'SentEmail', 'PasswordResetRequested', 'PasswordResetEmailConfirmed', 'PasswordResetCompleted' +) NOT NULL; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 03d8a40..6fcc599 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -263,6 +263,9 @@ enum EventLogType { AssignmentReplacementRequested AssignmentReplacementAccepted SentEmail + PasswordResetRequested + PasswordResetEmailConfirmed + PasswordResetCompleted } model EventLog { diff --git a/src/templates/emails/resetPass.hbs b/src/templates/emails/resetPass.hbs new file mode 100644 index 0000000..15b0125 --- /dev/null +++ b/src/templates/emails/resetPass.hbs @@ -0,0 +1,22 @@ +{{!-- Subject: ССОМ: Нужен е заместник--}} +{{!-- Text: Plain text version of your email. If not provided, HTML tags will be stripped from the HTML version for the +text version. --}} + +
+

Промяна на парола

+

Здравей, {{firstName}} {{lastName}}

+

+ Получихме заявка за промяна на паролата на твоя акаунт. Ако това не си ти, моля игнорирай този имейл. +

+

+ Смени + паролата си +

+ {{!--

Thank you very much for considering my request.

+

Best regards,
{{name}}

--}} +
+
+

Изпратено на: {{sentDate}}

+
\ No newline at end of file From c98b018bb1cf4e0642daed1ac23877e3650c25ac Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 30 Apr 2024 02:51:37 +0300 Subject: [PATCH 5/8] disable auto language detection; disable languuage switch for now; Tweak some error logs and messages --- components/languageSwitcher.tsx | 2 +- components/protectedRoute.tsx | 25 +++++++++++++----- components/sidebar.tsx | 39 +++++++++++++++++------------ next.config.js | 2 +- pages/_app.tsx | 15 +++++------ pages/api/data/[...nextcrud].ts | 1 + pages/cart/publishers/edit/[id].tsx | 14 ++++++++++- 7 files changed, 66 insertions(+), 32 deletions(-) diff --git a/components/languageSwitcher.tsx b/components/languageSwitcher.tsx index 4131523..b8cce90 100644 --- a/components/languageSwitcher.tsx +++ b/components/languageSwitcher.tsx @@ -39,7 +39,7 @@ const LanguageSwitcher = () => { open={Boolean(anchorEl)} onClose={handleClose} > - {locales.map((lng) => { + {locales?.map((lng) => { if (lng === locale) return null; return ( changeLanguage(lng)}> diff --git a/components/protectedRoute.tsx b/components/protectedRoute.tsx index 4c2adc9..6a6e787 100644 --- a/components/protectedRoute.tsx +++ b/components/protectedRoute.tsx @@ -10,9 +10,10 @@ interface ProtectedRouteProps { allowedRoles: UserRole[]; deniedMessage?: string; bypass?: boolean; + autoRedirect?: boolean; } -const ProtectedRoute = ({ children, allowedRoles, deniedMessage, bypass = false }: ProtectedRouteProps) => { +const ProtectedRoute = ({ children, allowedRoles, deniedMessage, bypass = false, autoRedirect = false }: ProtectedRouteProps) => { const { data: session, status } = useSession() const router = useRouter(); @@ -20,10 +21,9 @@ const ProtectedRoute = ({ children, allowedRoles, deniedMessage, bypass = false console.log("session.role:" + session?.user?.role); if (!status || status === "unauthenticated") { // Redirect to the sign-in page - if (!bypass) { + if (autoRedirect) { signIn(); } - return null; } else { console.log("session.role:" + session?.user?.role); @@ -41,14 +41,27 @@ const ProtectedRoute = ({ children, allowedRoles, deniedMessage, bypass = false if (deniedMessage !== undefined) { return
{deniedMessage}
; } - return
Нямате достъп до тази страница. Ако мислите, че това е грешка, моля, свържете се с администраторите
; + return ( + <> +
+
+

{session?.user?.email},

+

{`Нямате достъп до тази страница. Ако мислите, че това е грешка, моля, свържете се с администраторите`}

+
+
+ ); + } if (status === "loading") { return
Зареждане...
; } - if (!session) return Защитено съдържание. Впишете се.. - return children; + if (!session) { + if (deniedMessage !== undefined) { + return
{deniedMessage}
; + } + return Защитено съдържание. Впишете се.. + } }; export default ProtectedRoute; diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 57d86b0..34d2ceb 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -105,19 +105,22 @@ export default function Sidebar({ isSidebarOpen, toggleSidebar }) { useEffect(() => { const fetchLocations = async () => { try { - const response = await axiosInstance.get('/api/data/locations'); // Adjust the API endpoint as needed - const locationsData = response.data - .filter(location => location.isActive === true) - .map(location => ({ - text: location.name, - url: `/cart/locations/${location.id}`, - })); - // Find the "Locations" menu item and populate its children with locationsData - const menuIndex = sidemenu.findIndex(item => item.id === "locations"); - if (menuIndex !== -1) { - sidemenu[menuIndex].children = locationsData; + if (session) { // Don't fetch locations if the user is not authenticated + + const response = await axiosInstance.get('/api/data/locations'); // Adjust the API endpoint as needed + const locationsData = response.data + .filter(location => location.isActive === true) + .map(location => ({ + text: location.name, + url: `/cart/locations/${location.id}`, + })); + // Find the "Locations" menu item and populate its children with locationsData + const menuIndex = sidemenu.findIndex(item => item.id === "locations"); + if (menuIndex !== -1) { + sidemenu[menuIndex].children = locationsData; + } + //setLocations(locationsData); // Optional, if you need to use locations elsewhere } - //setLocations(locationsData); // Optional, if you need to use locations elsewhere } catch (error) { console.error("Error fetching locations:", error); } @@ -174,16 +177,17 @@ function UserSection({ session }) { } function SignInButton() { - const t = useTranslations('common'); + // const t = useTranslations('common'); return (
signIn()}> - + + {/* */}
); } function UserDetails({ session }) { - const t = useTranslations('common'); + // const t = useTranslations('common'); return ( <>
@@ -194,7 +198,10 @@ function UserDetails({ session }) { diff --git a/next.config.js b/next.config.js index 46f794f..bc25166 100644 --- a/next.config.js +++ b/next.config.js @@ -56,6 +56,6 @@ module.exports = withPWA({ // using https://next-intl-docs.vercel.app/docs/getting-started/pages-router locales: ['bg', 'en', 'ru'], defaultLocale: 'bg', - autoDetect: false, + localeDetection: false, }, }) \ No newline at end of file diff --git a/pages/_app.tsx b/pages/_app.tsx index 897ed80..e0934da 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -28,26 +28,27 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' export default function App({ Component, pageProps: { session, ...pageProps }, }: AppProps<{ session: Session }>) { // console.log(pageProps); const router = useRouter(); + const [locale, setLocale] = useState(router.locale || 'bg'); const [messages, setMessages] = useState({}); useEffect(() => { console.log("Current locale:", router.locale); async function loadLocaleData() { // Replace the static import with a fetch request - const res = await fetch(`/api/translations/${router.locale}`); + const res = await fetch(`/api/translations/${locale}`); if (res.ok) { const localeMessages = await res.json(); - console.log("Loaded messages for locale:", router.locale, localeMessages); + console.log("Loaded messages for locale:", locale, localeMessages); setMessages(localeMessages); } else { - const localeMessages = await import(`../content/i18n/${router.locale}.json`); setMessages(localeMessages.default); + const localeMessages = await import(`../content/i18n/${locale}.json`); setMessages(localeMessages.default); } - console.log("Loaded locale '", router.locale, "' ",); + console.log("Loaded locale '", locale, "' ",); //console.log("Loaded messages for locale:", router.locale, localeMessages.default); } loadLocaleData(); - }, [router.locale]); + }, [locale]); // useEffect(() => { // async function loadLocaleData() { @@ -101,7 +102,7 @@ export default function App({ Component, pageProps: { session, ...pageProps }, } return ( <> @@ -110,7 +111,7 @@ export default function App({ Component, pageProps: { session, ...pageProps }, } - + ) } diff --git a/pages/api/data/[...nextcrud].ts b/pages/api/data/[...nextcrud].ts index 31101c7..c3aa01b 100644 --- a/pages/api/data/[...nextcrud].ts +++ b/pages/api/data/[...nextcrud].ts @@ -33,6 +33,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'DELETE') { switch (targetTable) { case 'publishers': + case 'availabilities': const targetId = req.query.nextcrud[1]; logger.info('[nextCrud] ' + targetTable + ': ' + targetId + ' DELETED by ' + session.user.email); break; diff --git a/pages/cart/publishers/edit/[id].tsx b/pages/cart/publishers/edit/[id].tsx index c4605f9..331e52a 100644 --- a/pages/cart/publishers/edit/[id].tsx +++ b/pages/cart/publishers/edit/[id].tsx @@ -49,7 +49,19 @@ export const getServerSideProps = async (context) => { } var url = process.env.NEXT_PUBLIC_PUBLIC_URL + "/api/data/publishers/" + context.query.id + "?include=availabilities,assignments,assignments.shift"; console.log("GET PUBLISHER FROM:" + url) - const { data: item } = await axios.get(url); + try { + const { data: item } = await axios.get(url); + } catch (error) { + console.log("error fetching publisher: " + error); + //redirect to message page with message "no account found". get user from session + const user = context.req.session.user; + return { + redirect: { + destination: '/message?message=Този имейл (' + user.email + ') не е регистриран. Моля свържете се с администратора.', + permanent: false, + }, + } + } // item.allShifts = item.assignments.map((a: Assignment[]) => a.shift); From 3c751a42f050533b74bb7c1cdb1fe5c67f09b8f9 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 30 Apr 2024 02:51:51 +0300 Subject: [PATCH 6/8] text resources --- content/i18n/bg.json | 2 +- content/i18n/bg.modified.json | 34 ---------------------------------- content/i18n/en.json | 3 +++ content/i18n/en.modified.json | 15 --------------- 4 files changed, 4 insertions(+), 50 deletions(-) delete mode 100644 content/i18n/bg.modified.json delete mode 100644 content/i18n/en.modified.json diff --git a/content/i18n/bg.json b/content/i18n/bg.json index 22632aa..3a58787 100644 --- a/content/i18n/bg.json +++ b/content/i18n/bg.json @@ -4,7 +4,7 @@ "contacts": "Контакти", "greeting": "Здравей", "farewell": "Довиждане", - "changeTo": "-", + "changeTo": "", "BG": "Български", "EN": "Английски", "RU": "Руски", diff --git a/content/i18n/bg.modified.json b/content/i18n/bg.modified.json deleted file mode 100644 index 34515c1..0000000 --- a/content/i18n/bg.modified.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "common": { - "greeting": "Здравей", - "farewell": "Довиждане", - "changeTo": "", - "BG": "Български", - "EN": "Английски", - "RU": "Руски", - "login": "Вход", - "logout": "Изход" - }, - "menu": { - "dashboard": "Възможности", - "shedule": "График", - "myshedule": "Моя График", - "locations": "Местоположения", - "cart-report": "Отчет", - "cart-experience": "Случки", - "guidelines": "Напътствия", - "permits": "Разрешителни", - "contactAll": "Участници", - "feedback": "Отзиви", - "contactUs": "За връзка", - "admin": "Админ", - "cart-places": "Места", - "cart-publishers": "Вестители", - "cart-events": "План", - "cart-calendar": "Календар", - "cart-reports": "Отчети", - "statistics": "Статистика", - "coverMeLogs": "Замествания", - "translations": "Преводи" - } -} \ No newline at end of file diff --git a/content/i18n/en.json b/content/i18n/en.json index 626bb9e..9ca0d0e 100644 --- a/content/i18n/en.json +++ b/content/i18n/en.json @@ -8,5 +8,8 @@ "RU": "Russian", "login": "login", "logout": "logout" + }, + "menu": { + "dashboard": "Dashboard" } } \ No newline at end of file diff --git a/content/i18n/en.modified.json b/content/i18n/en.modified.json deleted file mode 100644 index 4a17b81..0000000 --- a/content/i18n/en.modified.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "common": { - "greeting": "Hello", - "farewell": "Goodbye", - "changeTo": "Change to", - "BG": "Bulgarian", - "EN": "English", - "RU": "Russian", - "login": "login", - "logout": "logout" - }, - "menu": { - "dashboard": "Dashboard" - } -} \ No newline at end of file From 01387cfe89f6ab6949687bed9b2e314794e20b30 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 30 Apr 2024 02:57:31 +0300 Subject: [PATCH 7/8] fix route, so we don't overwrite original files --- pages/cart/translations/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/cart/translations/index.tsx b/pages/cart/translations/index.tsx index 6929349..05282a6 100644 --- a/pages/cart/translations/index.tsx +++ b/pages/cart/translations/index.tsx @@ -22,7 +22,7 @@ const AdminTranslations = () => { }, [locale]); const handleSave = () => { - axiosInstance.post(`/api/translations/${locale}`, translations) + axiosInstance.post(`/api/translations/${locale}/modified`, translations) .then(res => { if (res.data.status === 'Updated') { toast.success('Translations updated!'); From 04060f4a4acd9372dae7e30515f7461594595a5e Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 30 Apr 2024 02:57:44 +0300 Subject: [PATCH 8/8] translations --- content/i18n/bg.modified.json | 36 +++++++++++++++++++++++++++++++++++ content/i18n/ru.json | 3 ++- content/i18n/ru.modified.json | 15 +++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 content/i18n/bg.modified.json create mode 100644 content/i18n/ru.modified.json diff --git a/content/i18n/bg.modified.json b/content/i18n/bg.modified.json new file mode 100644 index 0000000..26788cf --- /dev/null +++ b/content/i18n/bg.modified.json @@ -0,0 +1,36 @@ +{ + "common": { + "appNameLong": "Специално свидетелстване на обществени места в София", + "contacts": "Контакти", + "greeting": "Здравей", + "farewell": "Довиждане", + "changeTo": "", + "BG": "български", + "EN": "английски", + "RU": "руски", + "login": "Вход", + "logout": "Изход" + }, + "menu": { + "dashboard": "Възможности", + "shedule": "График", + "myshedule": "Моя График", + "locations": "Местоположения", + "cart-report": "Отчет", + "cart-experience": "Случки", + "guidelines": "Напътствия", + "permits": "Разрешителни", + "contactAll": "Участници", + "feedback": "Отзиви", + "contactUs": "За връзка", + "admin": "Админ", + "cart-places": "Места", + "cart-publishers": "Вестители", + "cart-events": "План", + "cart-calendar": "Календар", + "cart-reports": "Отчети", + "statistics": "Статистика", + "coverMeLogs": "Замествания", + "translations": "Преводи" + } +} \ No newline at end of file diff --git a/content/i18n/ru.json b/content/i18n/ru.json index a2f80ef..bae0120 100644 --- a/content/i18n/ru.json +++ b/content/i18n/ru.json @@ -6,7 +6,8 @@ "BG": "[RU] Български", "EN": "[RU] Английски", "RU": "[RU] Руски", - "login": "вход" + "login": "вход", + "contacts": "Контакти" }, "menu": { "dashboard": "Начало" diff --git a/content/i18n/ru.modified.json b/content/i18n/ru.modified.json new file mode 100644 index 0000000..3500f51 --- /dev/null +++ b/content/i18n/ru.modified.json @@ -0,0 +1,15 @@ +{ + "common": { + "greeting": "Здравей", + "farewell": "Довиждане", + "changeTo": "Смени на", + "BG": "болгарский", + "EN": "английский", + "RU": "русский", + "login": "вход", + "contacts": "Контакти te" + }, + "menu": { + "dashboard": "Начало" + } +} \ No newline at end of file