This commit is contained in:
dimitar 2025-06-24 21:36:56 +02:00
parent 02f97c8dfa
commit 06ffc05e2b
9 changed files with 257 additions and 186 deletions

View File

@ -4,10 +4,11 @@
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="postgresql://root:admin@localhost:5432/imk?schema=public"
DATABASE_URL="postgresql://root:irina76@localhost:5432/imk?schema=public"
JWT_SECRET=some-secret
AWS_REGION=EU2
AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4
AWS_S3_BUCKET_NAME=imk-data
AWS_ENDPOINT_URL=https://eu2.contabostorage.com

View File

@ -0,0 +1,82 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var client_1 = require("@prisma/client");
var bcrypt = require("bcrypt");
var prisma = new client_1.PrismaClient();
function main() {
return __awaiter(this, void 0, void 0, function () {
var hashedPassword, admin;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, bcrypt.hash('admin123', 10)];
case 1:
hashedPassword = _a.sent();
return [4 /*yield*/, prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
password: hashedPassword,
isAdmin: true,
},
})];
case 2:
admin = _a.sent();
console.log({ admin: admin });
return [2 /*return*/];
}
});
});
}
main()
.catch(function (e) {
console.error(e);
process.exit(1);
})
.finally(function () { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, prisma.$disconnect()];
case 1:
_a.sent();
return [2 /*return*/];
}
});
}); });

View File

@ -1,4 +1,13 @@
import { Controller, Get, Param, Req, Res, UseGuards, Logger, Request } from '@nestjs/common';
import {
Controller,
Get,
Param,
Req,
Res,
UseGuards,
Logger,
Request,
} from '@nestjs/common';
import { Response } from 'express';
import { DocumentsService } from './documents.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@ -17,7 +26,7 @@ export class DocumentsController {
constructor(
private readonly documentsService: DocumentsService,
private readonly s3Service: S3Service
private readonly s3Service: S3Service,
) {}
@Get('shared/:userId')
@ -31,7 +40,7 @@ export class DocumentsController {
async downloadDocument(
@Param('key') key: string,
@Request() req,
@Res() res: Response
@Res() res: Response,
) {
try {
this.logger.debug(`Download request for key: ${key}`);
@ -40,7 +49,8 @@ export class DocumentsController {
this.logger.debug(`Decoded key: ${decodedKey}`);
// Get document from database first to verify access
const document = await this.documentsService.findDocumentByS3Key(decodedKey);
const document =
await this.documentsService.findDocumentByS3Key(decodedKey);
if (!document) {
return res.status(404).json({ message: 'Document not found' });
@ -49,7 +59,7 @@ export class DocumentsController {
// Verify user has access to this document
const hasAccess = await this.documentsService.verifyDocumentAccess(
document.id,
req.user.id
req.user.id,
);
if (!hasAccess) {
@ -74,7 +84,7 @@ export class DocumentsController {
this.logger.error('Download error:', error);
return res.status(500).json({
message: 'Failed to download file',
error: error.message
error: error.message,
});
}
}

View File

@ -11,10 +11,12 @@
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"axios": "^1.7.7",
"date-fns": "^4.1.0",
"framer-motion": "^11.11.10",
"jwt-decode": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.5.0",
"react-router-dom": "^6.15.0"
},
"devDependencies": {
@ -1453,6 +1455,16 @@
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
"dev": true
},
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -3603,6 +3615,15 @@
"react": "^18.2.0"
}
},
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View File

@ -13,10 +13,12 @@
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"axios": "^1.7.7",
"date-fns": "^4.1.0",
"framer-motion": "^11.11.10",
"jwt-decode": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.5.0",
"react-router-dom": "^6.15.0"
},
"devDependencies": {

View File

@ -1,178 +1,129 @@
import { SectionHeader } from '../../shared/SectionHeader';
import { Button } from '../../shared/Button';
import { FiUsers, FiAward, FiCheckCircle, FiTrendingUp } from 'react-icons/fi';
import { NavLink } from "react-router-dom";
import { motion } from "framer-motion";
import { SectionHeader } from "../../shared/SectionHeader";
import { FiTrendingUp, FiAward, FiCheckCircle } from "react-icons/fi";
const milestones = [
{ year: "2008", title: "Основање на компанијата" },
{ year: "2012", title: "Проширување на лабораторијата" },
{ year: "2015", title: "ISO сертификација" },
{ year: "2018", title: "Воведување на нови услуги" },
{ year: "2020", title: "Модернизација на опремата" },
{ year: "2023", title: "Проширување на тимот" },
];
export default function About() {
const stats = [
{ label: 'Години искуство', value: '15+' },
{ label: 'Задоволни клиенти', value: '500+' },
{ label: 'Завршени проекти', value: '1000+' },
{ label: 'Експерти во тимот', value: '20+' }
];
const milestones = [
{ year: '2008', title: 'Основање на компанијата' },
{ year: '2012', title: 'Проширување на лабораторијата' },
{ year: '2015', title: 'ISO сертификација' },
{ year: '2018', title: 'Воведување на нови услуги' },
{ year: '2020', title: 'Модернизација на опремата' },
{ year: '2023', title: 'Проширување на тимот' }
];
return (
<div className="min-h-screen bg-gradient-to-b from-cyan-900 to-cyan-800">
<div className="isolate bg-graymin-h-screen bg-gradient-to-b from-cyan-900 to-cyan-400">
{/* Hero Section */}
<div className="relative h-[80vh] overflow-hidden">
<div className="relative h-screen overflow-hidden">
<div className="absolute inset-0">
<img
src="/about-hero.webp"
alt="Laboratory equipment"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-r from-blue-900/90 to-blue-600/80" />
</div>
<div className="relative container mx-auto px-4 h-full flex items-center">
<div className="max-w-3xl">
<h1 className="text-4xl md:text-6xl font-bold text-white mb-6">
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1, ease: "easeInOut" }}
className="text-4xl md:text-6xl font-bold text-white mb-6"
>
За Нас
</h1>
<p className="text-xl text-gray-200 mb-8">
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1, delay: 0.2, ease: "easeIn" }}
className="text-xl text-gray-200 mb-8"
>
Повеќе од 15 години искуство во обезбедување квалитет
</p>
</motion.p>
</div>
</div>
</div>
{/* Stats Section */}
{/* <div className="container mx-auto px-4 py-16 -mt-20">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-8">
{stats.map((stat, index) => (
<div key={index} className="bg-white/10 backdrop-blur-lg rounded-xl p-6 text-center">
<div className="text-3xl font-bold text-blue-400 mb-2">{stat.value}</div>
<div className="text-gray-300">{stat.label}</div>
</div>
))}
</div>
</div> */}
{/* Vision & Values Section */}
<div className="container mx-auto px-4 py-16">
<SectionHeader
title="Нашата Мисија"
subtitle="Посветени сме на обезбедување највисок квалитет во нашата работа"
className="text-white"
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-16">
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-8">
<div className="flex items-center mb-4">
<FiTrendingUp className="w-6 h-6 text-white mr-3" />
<h3 className="text-2xl font-semibold text-white">
Визија
</h3>
</div>
<p className="text-white text-xl">
Нашата визија е да бидеме водечка компанија во областа на испитување на материјали и контрола на квалитет во Македонија. Стремиме кон постојано унапредување на нашите услуги преку имплементација на најсовремени технологии и методологии. Сакаме да воспоставиме нови стандарди во индустријата и да допринесеме за развојот на градежништвото и инженерството во регионот.
</p>
</div>
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-8">
<div className="flex items-center mb-4">
<FiAward className="w-6 h-6 text-white mr-3" />
<h3 className="text-2xl font-semibold text-white">
Вредности
</h3>
</div>
<ul className="text-gray-300 space-y-3">
<li className="flex items-start text-xl">
<FiCheckCircle className="w-5 h-5 text-red mr-2 mt-1" />
<div>
<strong className="text-white">Квалитет</strong> - Посветени сме на обезбедување највисок квалитет во сите наши услуги
</div>
</li>
<li className="flex items-start">
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
<div>
<strong className="text-white">Интегритет</strong> - Работиме со целосна транспарентност и професионална етика
</div>
</li>
<li className="flex items-start">
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
<div>
<strong className="text-white">Иновација</strong> - Постојано инвестираме во нови технологии и методи
</div>
</li>
<li className="flex items-start">
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
<div>
<strong className="text-white">Експертиза</strong> - Нашиот тим се состои од високо квалификувани професионалци
</div>
</li>
<li className="flex items-start">
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
<div>
<strong className="text-white">Одговорност</strong> - Преземаме целосна одговорност за квалитетот на нашата работа
</div>
</li>
</ul>
</div>
</div>
{/* Timeline Section */}
<div className="mb-16 border rounded-xl border-red-900/20 p-10 bg-white/10 backdrop-blur-lg">
<div className="bg-white py-24">
<div className="container bg-gradient-to-b from-cyan-900 to-cyan-400 rounded-xl mx-auto p-20">
<SectionHeader
title="Нашиот Развој"
subtitle="Клучни моменти во нашата историја"
title="Нашата Мисија"
subtitle="Посветени сме на обезбедување највисок квалитет во нашата работа"
className="text-white"
/>
<div className="relative">
<div className="absolute left-1/2 transform -translate-x-1/2 h-full w-px"></div>
<div className="space-y-8">
{milestones.map((milestone, index) => (
<div key={index} className={`flex items-center ${index % 2 === 0 ? 'justify-start' : 'justify-end'}`}>
<div className={`w-1/2 ${index % 2 === 0 ? 'pr-8 text-right' : 'pl-8'}`}>
<div className="bg-white/80 backdrop-blur-lg rounded-xl p-6 text-blue-800">
<div className="text-blue-400 font-bold text-xl mb-2">{milestone.year}</div>
<div className="text-blue-700 text-xl">{milestone.title}</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Team Section */}
<div className="mb-16">
<SectionHeader
title="Нашиот Тим"
subtitle="Запознајте се со нашите експерти"
className="text-white"
/>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{[1, 2, 3].map((member) => (
<div key={member} className="bg-white backdrop-blur-lg rounded-xl p-6 text-center">
<div className="w-24 h-24 mx-auto mb-4 rounded-full bg-blue-400/20 flex items-center justify-center">
<FiUsers className="w-12 h-12 text-blue-400" />
</div>
<h4 className="text-white font-semibold mb-2">Име Презиме</h4>
<p className="text-gray-400">Позиција</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-16">
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-8">
<div className="flex items-center mb-4">
<FiTrendingUp className="w-6 h-6 text-white mr-3" />
<h3 className="text-2xl font-semibold text-white">Визија</h3>
</div>
))}
<p className="text-white text-xl">
Нашата визија е да бидеме водечка компанија во областа на
испитување на материјали и контрола на квалитет во Македонија.
Стремиме кон постојано унапредување на нашите услуги преку
имплементација на најсовремени технологии и методологии. Сакаме
да воспоставиме нови стандарди во индустријата и да допринесеме
за развојот на градежништвото и инженерството во регионот.
</p>
</div>
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-8">
<div className="flex items-center mb-4">
<FiAward className="w-6 h-6 text-white mr-3" />
<h3 className="text-2xl font-semibold text-white">Вредности</h3>
</div>
<ul className="text-gray-300 space-y-3">
<li className="flex items-start text-xl">
<FiCheckCircle className="w-5 h-5 text-red mr-2 mt-1" />
<div>
<strong className="text-white">Квалитет</strong> - Посветени
сме на обезбедување највисок квалитет во сите наши услуги
</div>
</li>
<li className="flex items-start">
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
<div>
<strong className="text-white">Интегритет</strong> -
Работиме со целосна транспарентност и професионална етика
</div>
</li>
<li className="flex items-start">
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
<div>
<strong className="text-white">Иновација</strong> -
Постојано инвестираме во нови технологии и методи
</div>
</li>
<li className="flex items-start">
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
<div>
<strong className="text-white">Експертиза</strong> - Нашиот
тим се состои од високо квалификувани професионалци
</div>
</li>
<li className="flex items-start">
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
<div>
<strong className="text-white">Одговорност</strong> -
Преземаме целосна одговорност за квалитетот на нашата работа
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
{/* CTA Section */}
<div className="bg-blue-600/20 backdrop-blur-lg py-24">
<div className="isolate bg-gray bg-gradient-to-b from-blue-700 to-blue-500 text-white py-24">
<div className="container mx-auto px-4 text-center">
<h2 className="text-3xl md:text-4xl font-bold text-white mb-8">
<h2 className="text-3xl md:text-4xl font-bold mb-8">
Започнете го вашиот проект со нас
</h2>
<Button variant="outline">
<NavLink
to="/contact"
className="inline-block bg-white text-blue-600 px-8 py-3 rounded-full font-semibold hover:bg-gray-100 transition-colors duration-300"
>
Контактирајте нѐ
</Button>
</NavLink>
</div>
</div>
</div>

View File

@ -5,7 +5,8 @@ import { SectionHeader } from "../../shared/SectionHeader";
const serviceCards = [
{
title: "Лабораториски услуги",
description: "Професионални лабораториски испитувања со најсовремена опрема",
description:
"Професионални лабораториски испитувања со најсовремена опрема",
image: "labfinal.webp",
link: "/lab",
services: [
@ -13,8 +14,8 @@ const serviceCards = [
"Геотехнички испитувања",
"Испитување на челик",
"Испитување на руди, метали и троски",
"Лабораториска екстракција од руди и троски"
]
"Лабораториска екстракција од руди и троски",
],
},
{
title: "Ултразвучни испитувања",
@ -26,8 +27,8 @@ const serviceCards = [
"Дебелометрија",
"Испитување на дефекти",
"Контрола на квалитет",
"Анализа и известување"
]
"Анализа и известување",
],
},
{
title: "Консалтинг услуги",
@ -39,18 +40,17 @@ const serviceCards = [
"Проектен менаџмент",
"Надзор на градба",
"Техничка документација",
"Анализа на материјали"
]
}
"Анализа на материјали",
],
},
];
export default function Home() {
return (
<div className="isolate bg-graymin-h-screen bg-gradient-to-b from-cyan-900 to-cyan-400">
{/* Hero Section with Parallax */}
<div className="relative h-[90vh] overflow-hidden">
<div className="relative h-screen overflow-hidden">
<div className="absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-r from-blue-900/90 to-blue-600/80" />
</div>
@ -69,7 +69,8 @@ export default function Home() {
transition={{ delay: 0.2 }}
className="text-xl text-gray-200 mb-8"
>
Професионални лабораториски услуги со најсовремена опрема и експертиза
Професионални лабораториски услуги со најсовремена опрема и
експертиза
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
@ -84,7 +85,7 @@ export default function Home() {
Контактирајте нѐ
</NavLink>
<NavLink
to="/services"
to="/about"
className="bg-white/10 hover:bg-white/20 text-white px-8 py-3 rounded-full font-semibold transition-colors duration-300"
>
Дознајте повеќе
@ -213,3 +214,4 @@ export default function Home() {
</div>
);
}

View File

@ -1,5 +1,5 @@
import { useState } from 'react';
import axios from 'axios';
import { useState } from "react";
import axios from "axios";
const FileUpload = () => {
const [file, setFile] = useState(null);
@ -16,25 +16,26 @@ const FileUpload = () => {
if (!file) return;
const formData = new FormData();
formData.append('file', file);
formData.append('title', 'Your document title');
// Add other fields as needed
formData.append("file", file);
formData.append("title", "Your document title");
try {
await axios.post('/api/admin/document', formData, {
await axios.post("/api/admin/document", formData, {
headers: {
'Content-Type': 'multipart/form-data',
"Content-Type": "multipart/form-data",
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
setProgress(percentCompleted);
},
});
console.log('Upload successful');
console.log("Upload successful");
setProgress(0);
} catch (error) {
console.error('Upload failed:', error);
console.error("Upload failed:", error);
setProgress(0);
}
};
@ -54,3 +55,4 @@ const FileUpload = () => {
};
export default FileUpload;