Merge branch 'feature-announcements'
This commit is contained in:
@ -39,7 +39,7 @@ const LanguageSwitcher = () => {
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{locales.map((lng) => {
|
||||
{locales?.map((lng) => {
|
||||
if (lng === locale) return null;
|
||||
return (
|
||||
<MenuItem key={lng} onClick={() => changeLanguage(lng)}>
|
||||
|
@ -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 <div>{deniedMessage}</div>;
|
||||
}
|
||||
return <div>Нямате достъп до тази страница. Ако мислите, че това е грешка, моля, свържете се с администраторите</div>;
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold mb-4 text-blue-500">{session?.user?.email},</h1>
|
||||
<p className="mb-6">{`Нямате достъп до тази страница. Ако мислите, че това е грешка, моля, свържете се с администраторите`}</p>
|
||||
</div>
|
||||
</div>
|
||||
</>);
|
||||
|
||||
}
|
||||
|
||||
if (status === "loading") {
|
||||
return <div>Зареждане...</div>;
|
||||
}
|
||||
if (!session) return <a href="/api/auth/signin">Защитено съдържание. Впишете се.. </a>
|
||||
return children;
|
||||
if (!session) {
|
||||
if (deniedMessage !== undefined) {
|
||||
return <div>{deniedMessage}</div>;
|
||||
}
|
||||
return <a href="/api/auth/signin">Защитено съдържание. Впишете се.. </a>
|
||||
}
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
|
@ -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 (
|
||||
<div className="items-center py-2 font-bold" onClick={() => signIn()}>
|
||||
<button>{t('login')}</button>
|
||||
<button>вход</button>
|
||||
{/* <button>{t('login')}</button> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UserDetails({ session }) {
|
||||
const t = useTranslations('common');
|
||||
// const t = useTranslations('common');
|
||||
return (
|
||||
<>
|
||||
<hr className="m-0" />
|
||||
@ -194,7 +198,10 @@ function UserDetails({ session }) {
|
||||
<div className="ml-3 overflow-hidden">
|
||||
<p className="mx-1 mt-1 text-sm font-medium text-gray-800 dark:text-gray-200">{session.user.name}</p>
|
||||
<p className="mx-1 mt-1 text-sm font-medium text-gray-600 dark:text-gray-400">{session.user.role}</p>
|
||||
<a href="/api/auth/signout" className={styles.button} onClick={(e) => { e.preventDefault(); signOut(); }}>{t('logout')}</a>
|
||||
<a href="/api/auth/signout" className={styles.button} onClick={(e) => { e.preventDefault(); signOut(); }}>
|
||||
{/* {t('logout')} */}
|
||||
изход
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -4,7 +4,7 @@
|
||||
"contacts": "Контакти",
|
||||
"greeting": "Здравей",
|
||||
"farewell": "Довиждане",
|
||||
"changeTo": "-",
|
||||
"changeTo": "",
|
||||
"BG": "Български",
|
||||
"EN": "Английски",
|
||||
"RU": "Руски",
|
||||
|
@ -1,11 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"appNameLong": "Специално свидетелстване на обществени места в София",
|
||||
"contacts": "Контакти",
|
||||
"greeting": "Здравей",
|
||||
"farewell": "Довиждане",
|
||||
"changeTo": "",
|
||||
"BG": "Български",
|
||||
"EN": "Английски",
|
||||
"RU": "Руски",
|
||||
"BG": "български",
|
||||
"EN": "английски",
|
||||
"RU": "руски",
|
||||
"login": "Вход",
|
||||
"logout": "Изход"
|
||||
},
|
||||
|
@ -8,5 +8,8 @@
|
||||
"RU": "Russian",
|
||||
"login": "login",
|
||||
"logout": "logout"
|
||||
},
|
||||
"menu": {
|
||||
"dashboard": "Dashboard"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -6,7 +6,8 @@
|
||||
"BG": "[RU] Български",
|
||||
"EN": "[RU] Английски",
|
||||
"RU": "[RU] Руски",
|
||||
"login": "вход"
|
||||
"login": "вход",
|
||||
"contacts": "Контакти"
|
||||
},
|
||||
"menu": {
|
||||
"dashboard": "Начало"
|
||||
|
15
content/i18n/ru.modified.json
Normal file
15
content/i18n/ru.modified.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"common": {
|
||||
"greeting": "Здравей",
|
||||
"farewell": "Довиждане",
|
||||
"changeTo": "Смени на",
|
||||
"BG": "болгарский",
|
||||
"EN": "английский",
|
||||
"RU": "русский",
|
||||
"login": "вход",
|
||||
"contacts": "Контакти te"
|
||||
},
|
||||
"menu": {
|
||||
"dashboard": "Начало"
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
})
|
276
package-lock.json
generated
276
package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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 (
|
||||
<>
|
||||
<NextIntlClientProvider
|
||||
locale={router.locale}
|
||||
locale={'bg'}
|
||||
timeZone="Europe/Sofia"
|
||||
messages={messages}
|
||||
>
|
||||
@ -110,7 +111,7 @@ export default function App({ Component, pageProps: { session, ...pageProps }, }
|
||||
<Component {...pageProps} />
|
||||
</LocalizationProvider>
|
||||
</SessionProvider>
|
||||
</NextIntlClientProvider>
|
||||
</NextIntlClientProvider >
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
@ -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;
|
||||
|
@ -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
|
||||
|
135
pages/auth/reset-password.tsx
Normal file
135
pages/auth/reset-password.tsx
Normal file
@ -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 (
|
||||
<Layout>
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="w-full max-w-md p-8 space-y-6 bg-white shadow-lg rounded-lg">
|
||||
<h1 className="text-xl font-bold text-center">Променете паролата си</h1>
|
||||
<form onSubmit={handleResetRequest} className="space-y-4">
|
||||
{!isConfirmed &&
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">имейл</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>}
|
||||
|
||||
{isConfirmed &&
|
||||
<div>
|
||||
<label htmlFor="newPassword" className="block text-sm font-medium text-gray-700">имейл</label>
|
||||
<input
|
||||
id="newPassword"
|
||||
type="password"
|
||||
required
|
||||
value={newPassword}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>}
|
||||
<div>
|
||||
<button type="submit" className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700">
|
||||
Изпрати линк за промяна на паролата
|
||||
</button>
|
||||
<button type="button" className="mt-4 w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-blue-600 hover:text-blue-700 focus:outline-none"
|
||||
onClick={() => window.location.href = '/auth/signin'}
|
||||
>
|
||||
страница за вход
|
||||
</button>
|
||||
</div>
|
||||
{message && <div className="text-center text-sm text-gray-500">{message}</div>}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
108
pages/auth/signin.tsx
Normal file
108
pages/auth/signin.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
// 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('Всички полета са задължителни');
|
||||
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 (
|
||||
<Layout>
|
||||
<div className="page">
|
||||
<div className="signin">
|
||||
<div className="min-h-screen flex flex-col items-center justify-center">
|
||||
{/* SSO Providers */}
|
||||
<div className="space-y-4 w-full px-4">
|
||||
<button onClick={() => signIn('google', { callbackUrl: '/' })}
|
||||
className="flex items-center justify-center w-full py-2 px-4 border border-gray-300 rounded shadow-sm text-sm text-gray-700 bg-white hover:bg-gray-50">
|
||||
<img loading="lazy" height="24" width="24" alt="Google logo"
|
||||
src="https://authjs.dev/img/providers/google.svg" className="mr-2" />
|
||||
Влез чрез Google
|
||||
</button>
|
||||
{/* Add more buttons for other SSO providers here in similar style */}
|
||||
</div>
|
||||
|
||||
{/* Email and Password Form */}
|
||||
<form onSubmit={handleSubmit} className="mt-8 w-full max-w-xs px-4">
|
||||
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
|
||||
<div className="mb-4">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">имейл</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">парола</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
{error && <div className="text-red-500 text-sm">{error}</div>}
|
||||
<button type="submit" className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700">
|
||||
Влез
|
||||
</button>
|
||||
{/* <button
|
||||
type="button"
|
||||
className="mt-4 w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-blue-600 hover:text-blue-700 focus:outline-none"
|
||||
onClick={() => router.push('/auth/reset-password')}>
|
||||
Забравена парола?
|
||||
</button> */}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
// This gets called on every request
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
csrfToken: await getCsrfToken(context),
|
||||
},
|
||||
};
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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!');
|
||||
|
3
prisma/migrations/20240429133812_/migration.sql
Normal file
3
prisma/migrations/20240429133812_/migration.sql
Normal file
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `User`
|
||||
ADD COLUMN `passwordHashLocalAccount` VARCHAR(191) NULL;
|
@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Eventlog`
|
||||
MODIFY `type` ENUM(
|
||||
'AssignmentReplacementRequested', 'AssignmentReplacementAccepted', 'SentEmail', 'PasswordResetRequested', 'PasswordResetEmailConfirmed', 'PasswordResetCompleted'
|
||||
) NOT NULL;
|
@ -263,6 +263,9 @@ enum EventLogType {
|
||||
AssignmentReplacementRequested
|
||||
AssignmentReplacementAccepted
|
||||
SentEmail
|
||||
PasswordResetRequested
|
||||
PasswordResetEmailConfirmed
|
||||
PasswordResetCompleted
|
||||
}
|
||||
|
||||
model EventLog {
|
||||
@ -278,13 +281,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
|
||||
|
22
src/templates/emails/resetPass.hbs
Normal file
22
src/templates/emails/resetPass.hbs
Normal file
@ -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. --}}
|
||||
|
||||
<section>
|
||||
<h3>Промяна на парола</h3>
|
||||
<p>Здравей, {{firstName}} {{lastName}}</p>
|
||||
<p>
|
||||
Получихме заявка за промяна на паролата на твоя акаунт. Ако това не си ти, моля игнорирай този имейл.
|
||||
</p>
|
||||
<p style="text-align: center;">
|
||||
<a href="{{resetUrl}}"
|
||||
target="_blank"
|
||||
style="background-color: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; display: inline-block; border-radius: 5px;">Смени
|
||||
паролата си</a>
|
||||
</p>
|
||||
{{!-- <p>Thank you very much for considering my request.</p>
|
||||
<p>Best regards,<br>{{name}}</p> --}}
|
||||
</section>
|
||||
<footer style="margin-top: 20px; text-align: center;">
|
||||
<p>Изпратено на: {{sentDate}}</p>
|
||||
</footer>
|
Reference in New Issue
Block a user