94 | I'll review your request and get back to you as soon as possible. Please
95 | feel free to reach out if you have any other questions in the meantime.
96 |
94 | I'll review your request and get back to you as soon as possible. Please
95 | feel free to reach out if you have any other questions in the meantime.
96 |
122 | );
123 | }
124 |
--------------------------------------------------------------------------------
/_utils/LocalStorage.jsx:
--------------------------------------------------------------------------------
1 | // export const Services = localStorage.getItem("data");
2 | /**
3 | * Stores data in localStorage with a 30-minute expiration time
4 | * @param {string} dataName - The key to store the data under
5 | * @param {any} data - The data to store
6 | */
7 | export const storeData = (dataName, data) => {
8 | // Create an object that includes the data and the expiration time
9 | const item = {
10 | value: data,
11 | // expiry: Date.now() + 30 * 60 * 1000, // Current time + 30 minutes in milliseconds
12 | expiry: Date.now() + 10 * 60 * 1000, // Current time + 10 minutes in milliseconds
13 | };
14 |
15 | // Store the object in localStorage
16 | localStorage.setItem(dataName, JSON.stringify(item));
17 | console.log("Data stored in localStorage with 30-minute expiration:", data);
18 | };
19 |
20 | /**
21 | * Retrieves data from localStorage if it exists and hasn't expired
22 | * @param {string} dataName - The key to retrieve data for
23 | * @returns {any|null} - The stored data or null if not found or expired
24 | */
25 | export const getData = (dataName) => {
26 | const itemStr = localStorage.getItem(dataName);
27 |
28 | // Return null if no item exists
29 | if (!itemStr) {
30 | console.log(`No data found in localStorage for: ${dataName}`);
31 | return null;
32 | }
33 |
34 | try {
35 | // Parse the item
36 | const item = JSON.parse(itemStr);
37 |
38 | // Check if the item is properly structured
39 | if (!item || item.value === undefined) {
40 | console.log(`Data is malformed for: ${dataName}`);
41 | return null;
42 | }
43 |
44 | // Check if the item has expired
45 | if (item.expiry && Date.now() > item.expiry) {
46 | // If expired, remove the item from localStorage and return null
47 | localStorage.removeItem(dataName);
48 | console.log(`Data expired and removed from localStorage: ${dataName}`);
49 | return null;
50 | }
51 |
52 | // If not expired and properly structured, return the value
53 | return item.value;
54 | } catch (error) {
55 | console.error(
56 | `Error parsing data from localStorage for: ${dataName}`,
57 | error
58 | );
59 | localStorage.removeItem(dataName);
60 | return null;
61 | }
62 | };
63 |
64 | export const SetAdminAccess = (data) => {
65 | console.log("Setting admin access in localStorage:", data);
66 | // Create an object that includes the data and the expiration time
67 | const item = {
68 | value: data,
69 | // expiry: Date.now() + 30 * 60 * 1000, // Current time + 30 minutes in milliseconds
70 | expiry: Date.now() + 120 * 60 * 1000, // Current time + 10 minutes in milliseconds
71 | };
72 |
73 | // Store the object in localStorage
74 | localStorage.setItem("adminAccess", JSON.stringify(item));
75 | console.log("Data stored in localStorage with 30-minute expiration:", data);
76 | };
77 |
78 | export const GetAdminAccess = () => {
79 | try {
80 | // Function to parse cookies from document.cookie
81 | const parseCookies = () => {
82 | const cookieObj = {};
83 | const cookies = document.cookie.split(";");
84 |
85 | for (let cookie of cookies) {
86 | const [name, value] = cookie.trim().split("=");
87 | if (name) cookieObj[name] = decodeURIComponent(value);
88 | }
89 |
90 | return cookieObj;
91 | };
92 |
93 | // Get all cookies
94 | const cookies = parseCookies();
95 |
96 | // Return null if no adminAccess cookie exists
97 | if (!cookies.adminAccess) {
98 | console.log(`No cookie found for: adminAccess`);
99 | return null;
100 | }
101 |
102 | // Parse the cookie value
103 | const item = JSON.parse(cookies.adminAccess);
104 |
105 | // Check if the item is properly structured
106 | if (!item || item.value === undefined) {
107 | console.log(`Cookie data is malformed for: adminAccess`);
108 | return null;
109 | }
110 |
111 | // Check if the item has expired
112 | if (item.expiry && Date.now() > item.expiry) {
113 | // If expired, remove the cookie and return null
114 | document.cookie =
115 | "adminAccess=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
116 | console.log(`Cookie expired and removed: adminAccess`);
117 | return null;
118 | }
119 |
120 | // If not expired and properly structured, return the value
121 | return item.value;
122 | } catch (error) {
123 | console.error(`Error parsing cookie data for: adminAccess`, error);
124 | // Remove potentially corrupted cookie
125 | document.cookie =
126 | "adminAccess=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
127 | return null;
128 | }
129 | };
130 |
--------------------------------------------------------------------------------
/public/fonts/Poppins/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | https://openfontlicense.org
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/app/api/Components/Azure.jsx:
--------------------------------------------------------------------------------
1 | import { BlobServiceClient } from "@azure/storage-blob";
2 |
3 | export async function uploadToAzure(file, fileName, container) {
4 | try {
5 | // Enhanced debugging
6 | console.log("Starting Azure upload process for:", fileName);
7 | console.log("File object type:", typeof file);
8 | console.log("File size:", file.size);
9 |
10 | // Get connection string from environment variables
11 | const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
12 | // Use consistent container name across functions
13 |
14 | const containerName = container || "projects";
15 |
16 | if (!connectionString) {
17 | console.error(
18 | "Azure Storage connection string is missing in environment variables"
19 | );
20 | throw new Error("Azure Storage connection string is missing");
21 | }
22 |
23 | // Create blob service client with improved error handling
24 | const blobServiceClient =
25 | BlobServiceClient.fromConnectionString(connectionString);
26 | const containerClient = blobServiceClient.getContainerClient(containerName);
27 |
28 | // Create container if it doesn't exist
29 | console.log("Ensuring container exists:", containerName);
30 | await containerClient.createIfNotExists({
31 | access: "blob", // Public access at blob level
32 | });
33 |
34 | // Generate unique blob name to avoid overwrites
35 | const uniqueName = `${Date.now()}-${fileName.replace(/[^\w.-]/g, "")}`;
36 | const blockBlobClient = containerClient.getBlockBlobClient(uniqueName);
37 |
38 | // Convert File to buffer if needed with better error handling
39 | let buffer;
40 | let contentType;
41 |
42 | try {
43 | if (file instanceof Buffer) {
44 | buffer = file;
45 | contentType = "application/octet-stream";
46 | } else {
47 | buffer = Buffer.from(await file.arrayBuffer());
48 | contentType = file.type || "application/octet-stream";
49 |
50 | // Better content type detection for common image types
51 | if (!file.type && fileName) {
52 | const extension = fileName.split(".").pop().toLowerCase();
53 | const imageTypes = {
54 | png: "image/png",
55 | jpg: "image/jpeg",
56 | jpeg: "image/jpeg",
57 | gif: "image/gif",
58 | webp: "image/webp",
59 | };
60 | contentType = imageTypes[extension] || contentType;
61 | }
62 | }
63 | } catch (bufferError) {
64 | console.error("Error creating buffer from file:", bufferError);
65 | throw new Error(`Failed to process file: ${bufferError.message}`);
66 | }
67 |
68 | // Upload with improved options
69 | console.log("Uploading blob to Azure...");
70 | await blockBlobClient.upload(buffer, buffer.length, {
71 | blobHTTPHeaders: {
72 | blobContentType: contentType,
73 | blobCacheControl: "max-age=86400", // 1 day cache
74 | },
75 | metadata: {
76 | filename: fileName,
77 | uploadDate: new Date().toISOString(),
78 | },
79 | });
80 |
81 | console.log("File uploaded to Azure Blob Storage:", blockBlobClient.url);
82 |
83 | return {
84 | success: true,
85 | url: blockBlobClient.url,
86 | name: uniqueName,
87 | };
88 | } catch (error) {
89 | console.error("Azure upload error:", error);
90 | return {
91 | success: false,
92 | error: error.message,
93 | };
94 | }
95 | }
96 |
97 | // Use same container name for consistency as in uploadToAzure
98 | export async function deleteFromAzure(blobName) {
99 | try {
100 | const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
101 | // Use same container name as upload function
102 | const containerName =
103 | process.env.AZURE_STORAGE_CONTAINER_NAME || "portfolio";
104 |
105 | const blobServiceClient =
106 | BlobServiceClient.fromConnectionString(connectionString);
107 | const containerClient = blobServiceClient.getContainerClient(containerName);
108 | const blockBlobClient = containerClient.getBlockBlobClient(blobName);
109 |
110 | await blockBlobClient.delete();
111 | console.log("Successfully deleted blob:", blobName);
112 |
113 | return { success: true };
114 | } catch (error) {
115 | console.error("Azure delete error:", error);
116 | return {
117 | success: false,
118 | error: error.message,
119 | };
120 | }
121 | }
122 |
123 | // Helper function to extract blob name from URL (helpful for deletions)
124 | export function getBlobNameFromUrl(url) {
125 | if (!url) return null;
126 | const urlParts = url.split("/");
127 | return urlParts[urlParts.length - 1];
128 | }
129 |
--------------------------------------------------------------------------------
/public/fonts/Tagesschrift/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2024 The Tagesschrift Project Authors (https://github.com/yanone/tagesschrift)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | https://openfontlicense.org
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/public/fonts/Space_Grotesk/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | https://openfontlicense.org
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/public/fonts/Playfair_Display/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2017 The Playfair Display Project Authors (https://github.com/clauseggers/Playfair-Display), with Reserved Font Name "Playfair Display"
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | https://openfontlicense.org
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/app/api/events/[id]/route.js:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { getDatabases } from "@/_utils/Mongodb";
3 | import { withAuth } from "@/_utils/auth";
4 | import { ObjectId } from "mongodb";
5 | import { deleteFromAzure, getBlobNameFromUrl } from "../../Components/Azure";
6 |
7 | // Get a single event by ID
8 | async function getHandler(request, { params }) {
9 | try {
10 | const { eventsCollection } = await getDatabases();
11 | const id = params.id;
12 | console.log("Fetching event with ID:", id);
13 |
14 | // Check if ID is valid ObjectId
15 | if (!ObjectId.isValid(id)) {
16 | return NextResponse.json({ error: "Invalid event ID" }, { status: 400 });
17 | }
18 |
19 | const event = await eventsCollection.findOne({ _id: new ObjectId(id) });
20 | console.log("Event fetched:", event);
21 |
22 | if (!event) {
23 | return NextResponse.json({ error: "Event not found" }, { status: 404 });
24 | }
25 |
26 | return NextResponse.json({ event }, { status: 200 });
27 | } catch (error) {
28 | console.error("Error fetching event:", error);
29 | return NextResponse.json(
30 | { error: "Failed to fetch event" },
31 | { status: 500 }
32 | );
33 | }
34 | }
35 |
36 | // Delete an event by ID
37 | async function deleteHandler(request, { params }) {
38 | try {
39 | const { eventsCollection } = await getDatabases();
40 | const id = params.id;
41 |
42 | // Check if ID is valid ObjectId
43 | if (!ObjectId.isValid(id)) {
44 | return NextResponse.json({ error: "Invalid event ID" }, { status: 400 });
45 | }
46 |
47 | // Find the event first to get the image URL for deletion from Azure
48 | const event = await eventsCollection.findOne({ _id: new ObjectId(id) });
49 |
50 | if (!event) {
51 | return NextResponse.json({ error: "Event not found" }, { status: 404 });
52 | }
53 |
54 | // Delete the event from the database
55 | const result = await eventsCollection.deleteOne({ _id: new ObjectId(id) });
56 |
57 | if (result.deletedCount === 0) {
58 | return NextResponse.json(
59 | { error: "Failed to delete event" },
60 | { status: 500 }
61 | );
62 | }
63 |
64 | // If the event has an image, delete it from Azure Blob Storage
65 | if (event.image) {
66 | try {
67 | const blobName = getBlobNameFromUrl(event.image);
68 | if (blobName) {
69 | console.log("Deleting image from Azure:", blobName);
70 | // Pass the "events" container name to ensure we're deleting from the correct container
71 | await deleteFromAzure(blobName, "events");
72 | }
73 | } catch (imageError) {
74 | console.error("Error deleting image from Azure:", imageError);
75 | // Continue with success response even if image deletion fails
76 | }
77 | }
78 |
79 | return NextResponse.json(
80 | { message: "Event deleted successfully" },
81 | { status: 200 }
82 | );
83 | } catch (error) {
84 | console.error("Error deleting event:", error);
85 | return NextResponse.json(
86 | { error: "Failed to delete event" },
87 | { status: 500 }
88 | );
89 | }
90 | }
91 |
92 | // Update an event by ID
93 | async function putHandler(request, { params }) {
94 | try {
95 | const { eventsCollection } = await getDatabases();
96 | const id = params.id;
97 | const eventData = await request.json();
98 |
99 | // Check if ID is valid ObjectId
100 | if (!ObjectId.isValid(id)) {
101 | return NextResponse.json({ error: "Invalid event ID" }, { status: 400 });
102 | }
103 |
104 | // Validate required fields
105 | if (
106 | !eventData.name ||
107 | !eventData.host ||
108 | !eventData.date ||
109 | !eventData.location ||
110 | !eventData.skills ||
111 | !Array.isArray(eventData.skills) ||
112 | !eventData.category
113 | ) {
114 | return NextResponse.json(
115 | { error: "Missing required fields" },
116 | { status: 400 }
117 | );
118 | }
119 |
120 | // Add update timestamp
121 | eventData.updatedAt = new Date().toISOString();
122 |
123 | // Update the event
124 | const result = await eventsCollection.updateOne(
125 | { _id: new ObjectId(id) },
126 | { $set: eventData }
127 | );
128 |
129 | if (result.matchedCount === 0) {
130 | return NextResponse.json({ error: "Event not found" }, { status: 404 });
131 | }
132 |
133 | return NextResponse.json(
134 | { message: "Event updated successfully" },
135 | { status: 200 }
136 | );
137 | } catch (error) {
138 | console.error("Error updating event:", error);
139 | return NextResponse.json(
140 | { error: "Failed to update event" },
141 | { status: 500 }
142 | );
143 | }
144 | }
145 |
146 | // Export handlers with authentication middleware
147 | export const GET = getHandler;
148 | export const DELETE = withAuth(deleteHandler); // Only authenticated users can delete
149 | export const PUT = withAuth(putHandler); // Only authenticated users can update
150 |
--------------------------------------------------------------------------------
/app/api/positions/route.js:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { getDatabases } from "@/_utils/Mongodb";
3 | import { withAuth } from "@/_utils/auth";
4 | import { uploadToAzure } from "../Components/Azure";
5 |
6 | // Helper for multipart form data
7 | async function parseMultipartForm(request) {
8 | try {
9 | const formData = await request.formData();
10 | const data = {};
11 | let logoFile = null;
12 |
13 | // Extract form fields
14 | for (const [key, value] of formData.entries()) {
15 | if (key === "logo" && value.size > 0) {
16 | logoFile = value;
17 | } else if (key === "responsibilities") {
18 | data[key] = JSON.parse(value);
19 | } else if (key === "isRemote" || key === "isCurrent") {
20 | data[key] = value === "true";
21 | } else {
22 | data[key] = value;
23 | }
24 | }
25 |
26 | return { data, logoFile };
27 | } catch (error) {
28 | console.error("Error parsing multipart form:", error);
29 | throw error;
30 | }
31 | }
32 |
33 | // GET all positions
34 | async function getHandler() {
35 | try {
36 | const { positionsCollection } = await getDatabases();
37 |
38 | // Get all positions and sort by startDate in descending order (newest first)
39 | const positions = await positionsCollection
40 | .find({})
41 | .sort({ isCurrent: -1, startDate: -1 })
42 | .toArray();
43 |
44 | return NextResponse.json({ positions }, { status: 200 });
45 | } catch (error) {
46 | console.error("Error fetching positions:", error);
47 | return NextResponse.json(
48 | { error: "Failed to fetch positions" },
49 | { status: 500 }
50 | );
51 | }
52 | }
53 |
54 | // POST a new position
55 | async function postHandler(request) {
56 | try {
57 | const { positionsCollection } = await getDatabases();
58 |
59 | // For multipart form data with logo upload
60 | const { data, logoFile } = await parseMultipartForm(request);
61 | console.log("Parsed form data:", data);
62 | console.log(
63 | "Parsed logo file:",
64 | logoFile ? `${logoFile.name} (${logoFile.size} bytes)` : "None"
65 | );
66 |
67 | // Validate required fields
68 | if (
69 | !data.companyName ||
70 | !data.jobTitle ||
71 | !data.employmentType ||
72 | !data.startDate ||
73 | !data.location ||
74 | !data.responsibilities ||
75 | data.responsibilities.length === 0
76 | ) {
77 | return NextResponse.json(
78 | { error: "Missing required fields" },
79 | { status: 400 }
80 | );
81 | }
82 |
83 | // Position data structure
84 | const positionData = {
85 | companyName: data.companyName,
86 | jobTitle: data.jobTitle,
87 | employmentType: data.employmentType,
88 | startDate: data.startDate,
89 | endDate: data.isCurrent ? null : data.endDate,
90 | isCurrent: data.isCurrent,
91 | location: data.location,
92 | isRemote: data.isRemote,
93 | responsibilities: data.responsibilities,
94 | logoUrl: null,
95 | createdAt: new Date().toISOString(),
96 | updatedAt: new Date().toISOString(),
97 | };
98 |
99 | // Upload logo to Azure if provided
100 | if (logoFile) {
101 | try {
102 | console.log("Uploading position logo to Azure:", logoFile.name);
103 |
104 | // Use the specific container for position logos
105 | const uploadResult = await uploadToAzure(
106 | logoFile,
107 | logoFile.name,
108 | "positions"
109 | );
110 |
111 | if (uploadResult.success) {
112 | positionData.logoUrl = uploadResult.url;
113 | console.log("Position logo uploaded successfully:", uploadResult.url);
114 | } else {
115 | console.error("Azure upload failed:", uploadResult.error);
116 | return NextResponse.json(
117 | { error: "Failed to upload logo" },
118 | { status: 500 }
119 | );
120 | }
121 | } catch (uploadError) {
122 | console.error("Error uploading position logo:", uploadError);
123 | return NextResponse.json(
124 | { error: "Error uploading logo: " + uploadError.message },
125 | { status: 500 }
126 | );
127 | }
128 | }
129 |
130 | // Insert into database
131 | const result = await positionsCollection.insertOne(positionData);
132 |
133 | return NextResponse.json(
134 | {
135 | message: "Position created successfully",
136 | positionId: result.insertedId,
137 | position: positionData,
138 | },
139 | { status: 201 }
140 | );
141 | } catch (error) {
142 | console.error("Error creating position:", error);
143 | return NextResponse.json(
144 | { error: "Failed to create position" },
145 | { status: 500 }
146 | );
147 | }
148 | }
149 |
150 | // Export the handlers with authentication middleware
151 | export const GET = getHandler; // Anyone can view positions
152 | export const POST = withAuth(postHandler); // Only authenticated users can create positions
153 |
--------------------------------------------------------------------------------
/components/EventDescription.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import dynamic from "next/dynamic";
4 | import React, { useState, useEffect } from "react";
5 | import { commands } from "@uiw/react-md-editor";
6 | import { FaImage } from "react-icons/fa";
7 |
8 | // Dynamically import the Markdown editor with SSR disabled
9 | const MDEditor = dynamic(() => import("@uiw/react-md-editor"), { ssr: false });
10 | const MarkdownPreview = dynamic(() => import("@uiw/react-markdown-preview"), {
11 | ssr: false,
12 | });
13 |
14 | export default function EventDescription({ value, onChange, placeholder }) {
15 | const [isUploading, setIsUploading] = useState(false);
16 |
17 | // Custom image upload command for markdown editor
18 | const imageCommand = {
19 | name: "image-upload",
20 | keyCommand: "image-upload",
21 | buttonProps: { "aria-label": "Upload image" },
22 | icon: ,
23 | execute: async (state, api) => {
24 | const file = await selectImage();
25 | if (!file) return;
26 |
27 | // Show temporary local blob preview
28 | const tempUrl = URL.createObjectURL(file);
29 | const tempMarkdown = `\n`;
30 | api.replaceSelection(tempMarkdown);
31 |
32 | // Upload to Azure Blob Storage with retry mechanism
33 | setIsUploading(true);
34 | let retries = 0;
35 | const maxRetries = 3;
36 |
37 | while (retries < maxRetries) {
38 | try {
39 | const formData = new FormData();
40 | formData.append("file", file);
41 |
42 | // Include metadata for Azure Blob Storage
43 | const timestamp = new Date().getTime();
44 | const fileExtension = file.name.split(".").pop().toLowerCase();
45 | const fileName = `events/${new Date().getFullYear()}/${
46 | new Date().getMonth() + 1
47 | }/${timestamp}-${file.name.replace(/\s+/g, "-")}`;
48 | formData.append("fileName", fileName);
49 | formData.append("contentType", file.type);
50 | formData.append("container", "events");
51 |
52 | // Add additional metadata as a JSON string
53 | const metadata = {
54 | uploadedAt: new Date().toISOString(),
55 | context: "event-content-image",
56 | position: state.selection ? String(state.selection.start) : "0",
57 | fileSize: String(file.size),
58 | };
59 |
60 | formData.append("metadata", JSON.stringify(metadata));
61 |
62 | const res = await fetch(
63 | `${process.env.NEXT_PUBLIC_BASE_URL}/api/upload`,
64 | {
65 | method: "POST",
66 | body: formData,
67 | }
68 | );
69 |
70 | if (!res.ok) {
71 | if (res.status >= 500 && retries < maxRetries - 1) {
72 | // Retry server errors
73 | retries++;
74 | // Exponential backoff
75 | await new Promise((r) =>
76 | setTimeout(r, 1000 * Math.pow(2, retries))
77 | );
78 | continue;
79 | }
80 | throw new Error(`Image upload failed: ${res.status}`);
81 | }
82 |
83 | const data = await res.json();
84 | if (data?.url) {
85 | const finalMarkdown = `\n`;
86 | api.replaceSelection(finalMarkdown);
87 | }
88 | break; // Success, exit retry loop
89 | } catch (error) {
90 | console.error(
91 | `Error uploading image (attempt ${retries + 1}):`,
92 | error
93 | );
94 | if (retries >= maxRetries - 1) {
95 | api.replaceSelection(`![Upload failed]()\n`);
96 | alert(
97 | `Image upload failed after ${maxRetries} attempts: ${error.message}`
98 | );
99 | } else {
100 | retries++;
101 | // Exponential backoff
102 | await new Promise((r) =>
103 | setTimeout(r, 1000 * Math.pow(2, retries))
104 | );
105 | }
106 | }
107 | }
108 |
109 | setIsUploading(false);
110 | },
111 | };
112 |
113 | const selectImage = () =>
114 | new Promise((resolve) => {
115 | const input = document.createElement("input");
116 | input.type = "file";
117 | input.accept = "image/*";
118 | input.onchange = () => resolve(input.files[0]);
119 | input.click();
120 | });
121 |
122 | return (
123 |
124 |
140 | {isUploading && (
141 |
Uploading image...
142 | )}
143 |
144 | );
145 | }
146 |
--------------------------------------------------------------------------------
/app/api/projects/route.js:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { getDatabases } from "@/_utils/Mongodb";
3 | import { withAuth } from "@/_utils/auth";
4 | import { uploadToAzure } from "../Components/Azure";
5 |
6 | // Helper for multipart form data
7 | async function parseMultipartForm(request) {
8 | try {
9 | const formData = await request.formData();
10 | console.log(
11 | "Form data entries:",
12 | [...formData.entries()].map(([k, v]) =>
13 | k === "image" ? `${k}: [File]` : `${k}: ${v}`
14 | )
15 | );
16 |
17 | // Extract form fields
18 | const data = {
19 | title: formData.get("title"),
20 | description: formData.get("description"),
21 | githubUrl: formData.get("githubUrl"),
22 | liveUrl: formData.get("liveUrl"),
23 | featured: formData.get("featured") === "true",
24 | tags: JSON.parse(formData.get("tags") || "[]"),
25 | };
26 |
27 | // Handle image file if present - use "image" field name to match frontend
28 | const imageFile = formData.get("image");
29 | console.log(
30 | "Image file found:",
31 | imageFile
32 | ? `${imageFile.name} (${imageFile.size} bytes)`
33 | : "No image file"
34 | );
35 |
36 | return { data, imageFile };
37 | } catch (error) {
38 | console.error("Error parsing form data:", error);
39 | throw error;
40 | }
41 | }
42 |
43 | // Handler function for creating a new project
44 | async function postHandler(request) {
45 | try {
46 | const { projectsCollection } = await getDatabases();
47 |
48 | // For multipart form data with image upload
49 | const { data, imageFile } = await parseMultipartForm(request);
50 | console.log("Parsed form data:", data);
51 | console.log(
52 | "Parsed image file:",
53 | imageFile ? `${imageFile.name} (${imageFile.size} bytes)` : "None"
54 | );
55 |
56 | // Validate required fields
57 | if (
58 | !data.title ||
59 | !data.description ||
60 | !data.githubUrl ||
61 | data.tags.length === 0
62 | ) {
63 | return NextResponse.json(
64 | { error: "Missing required fields" },
65 | { status: 400 }
66 | );
67 | }
68 |
69 | // Project data structure
70 | const projectData = {
71 | title: data.title,
72 | description: data.description,
73 | githubUrl: data.githubUrl,
74 | liveUrl: data.liveUrl || "",
75 | featured: data.featured,
76 | tags: data.tags,
77 | imageUrl: null, // Will be updated if there's an image
78 | createdAt: new Date(),
79 | updatedAt: new Date(),
80 | };
81 |
82 | // Handle image upload if provided
83 | if (imageFile && imageFile.size > 0) {
84 | try {
85 | console.log("Uploading image to Azure:", imageFile.name);
86 | const uploadResult = await uploadToAzure(imageFile, imageFile.name);
87 | console.log("Azure upload result:", uploadResult);
88 |
89 | if (uploadResult.success) {
90 | // Set the image URL from Azure
91 | projectData.imageUrl = uploadResult.url;
92 | console.log("Successfully set imageUrl:", uploadResult.url);
93 | } else {
94 | console.error(
95 | "Azure upload failed but continuing with null image URL:",
96 | uploadResult.error
97 | );
98 | // Don't return early, just log the error and continue with null imageUrl
99 | }
100 | } catch (uploadError) {
101 | console.error("Error during image upload:", uploadError);
102 | // Don't return early, just log the error and continue with null imageUrl
103 | }
104 | }
105 |
106 | // Insert into database regardless of image upload success
107 | console.log("Inserting project with data:", JSON.stringify(projectData));
108 | const result = await projectsCollection.insertOne(projectData);
109 |
110 | // Return success response
111 | return NextResponse.json(
112 | {
113 | message: "Project created successfully",
114 | projectId: result.insertedId,
115 | imageUrl: projectData.imageUrl, // Return the image URL in the response
116 | },
117 | { status: 201 }
118 | );
119 | } catch (error) {
120 | console.error("Error creating project:", error);
121 | return NextResponse.json(
122 | { error: "Failed to create project" },
123 | { status: 500 }
124 | );
125 | }
126 | }
127 |
128 | // Handler function for getting all projects
129 | async function getHandler() {
130 | try {
131 | const { projectsCollection } = await getDatabases();
132 |
133 | // Get all projects
134 | const projects = await projectsCollection
135 | .find({})
136 | .sort({ createdAt: -1 })
137 | .toArray();
138 |
139 | // Return projects
140 | return NextResponse.json({ projects }, { status: 200 });
141 | } catch (error) {
142 | console.error("Error fetching projects:", error);
143 | return NextResponse.json(
144 | { error: "Failed to fetch projects" },
145 | { status: 500 }
146 | );
147 | }
148 | }
149 |
150 | // Export the handlers with authentication middleware
151 | // Only authenticated users can create projects
152 | export const POST = withAuth(postHandler);
153 |
154 | // Anyone can view projects
155 | export const GET = getHandler;
156 |
--------------------------------------------------------------------------------
/app/api/linkedin/[id]/route.js:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { getDatabases } from "@/_utils/Mongodb";
3 | import { withAuth } from "@/_utils/auth";
4 | import { ObjectId } from "mongodb";
5 | import { deleteFromAzure, getBlobNameFromUrl } from "../../Components/Azure";
6 |
7 | // Get a single LinkedIn post by ID
8 | async function getHandler(request, { params }) {
9 | try {
10 | const { linkedinCollection } = await getDatabases();
11 | const id = params.id;
12 |
13 | // Validate ObjectId format
14 | if (!ObjectId.isValid(id)) {
15 | return NextResponse.json(
16 | { error: "Invalid LinkedIn post ID" },
17 | { status: 400 }
18 | );
19 | }
20 |
21 | // Find the post
22 | const post = await linkedinCollection.findOne({ _id: new ObjectId(id) });
23 |
24 | if (!post) {
25 | return NextResponse.json(
26 | { error: "LinkedIn post not found" },
27 | { status: 404 }
28 | );
29 | }
30 |
31 | return NextResponse.json({ post }, { status: 200 });
32 | } catch (error) {
33 | console.error("Error fetching LinkedIn post:", error);
34 | return NextResponse.json(
35 | { error: "Failed to fetch LinkedIn post" },
36 | { status: 500 }
37 | );
38 | }
39 | }
40 |
41 | // Delete a LinkedIn post by ID
42 | async function deleteHandler(request, { params }) {
43 | try {
44 | const { linkedinCollection } = await getDatabases();
45 | const id = params.id;
46 |
47 | // Validate ObjectId format
48 | if (!ObjectId.isValid(id)) {
49 | return NextResponse.json(
50 | { error: "Invalid LinkedIn post ID" },
51 | { status: 400 }
52 | );
53 | }
54 |
55 | // Find the post first to get the image URL for Azure deletion
56 | const post = await linkedinCollection.findOne({ _id: new ObjectId(id) });
57 |
58 | if (!post) {
59 | return NextResponse.json(
60 | { error: "LinkedIn post not found" },
61 | { status: 404 }
62 | );
63 | }
64 |
65 | // Delete the post from MongoDB
66 | const result = await linkedinCollection.deleteOne({
67 | _id: new ObjectId(id),
68 | });
69 |
70 | if (result.deletedCount === 0) {
71 | return NextResponse.json(
72 | { error: "Failed to delete LinkedIn post" },
73 | { status: 500 }
74 | );
75 | }
76 |
77 | // Delete the image from Azure Blob Storage if it exists
78 | if (post.image) {
79 | try {
80 | const blobName = getBlobNameFromUrl(post.image);
81 | if (blobName) {
82 | console.log("Deleting LinkedIn post image from Azure:", blobName);
83 | // Use the specific "linkedinevents" container for LinkedIn post images
84 | await deleteFromAzure(blobName, "linkedinevents");
85 | console.log("LinkedIn post image deleted successfully");
86 | }
87 | } catch (imageError) {
88 | console.error(
89 | "Error deleting LinkedIn post image from Azure:",
90 | imageError
91 | );
92 | // Continue with success response even if image deletion fails
93 | }
94 | }
95 |
96 | return NextResponse.json(
97 | { message: "LinkedIn post deleted successfully" },
98 | { status: 200 }
99 | );
100 | } catch (error) {
101 | console.error("Error deleting LinkedIn post:", error);
102 | return NextResponse.json(
103 | { error: "Failed to delete LinkedIn post" },
104 | { status: 500 }
105 | );
106 | }
107 | }
108 |
109 | // Update a LinkedIn post by ID
110 | async function putHandler(request, { params }) {
111 | try {
112 | const { linkedinCollection } = await getDatabases();
113 | const id = params.id;
114 | const updateData = await request.json();
115 |
116 | // Validate ObjectId format
117 | if (!ObjectId.isValid(id)) {
118 | return NextResponse.json(
119 | { error: "Invalid LinkedIn post ID" },
120 | { status: 400 }
121 | );
122 | }
123 |
124 | // Basic validation of update data
125 | if (!updateData.title || !updateData.description) {
126 | return NextResponse.json(
127 | { error: "Title and description are required" },
128 | { status: 400 }
129 | );
130 | }
131 |
132 | // Add updated timestamp
133 | updateData.updatedAt = new Date().toISOString();
134 |
135 | // Update the post
136 | const result = await linkedinCollection.updateOne(
137 | { _id: new ObjectId(id) },
138 | { $set: updateData }
139 | );
140 |
141 | if (result.matchedCount === 0) {
142 | return NextResponse.json(
143 | { error: "LinkedIn post not found" },
144 | { status: 404 }
145 | );
146 | }
147 |
148 | return NextResponse.json(
149 | { message: "LinkedIn post updated successfully" },
150 | { status: 200 }
151 | );
152 | } catch (error) {
153 | console.error("Error updating LinkedIn post:", error);
154 | return NextResponse.json(
155 | { error: "Failed to update LinkedIn post" },
156 | { status: 500 }
157 | );
158 | }
159 | }
160 |
161 | // Export handlers with authentication middleware
162 | export const GET = getHandler; // Anyone can get a LinkedIn post
163 | export const DELETE = withAuth(deleteHandler); // Only authenticated users can delete
164 | export const PUT = withAuth(putHandler); // Only authenticated users can update
165 |
--------------------------------------------------------------------------------
/app/api/blogs/[id]/route.js:
--------------------------------------------------------------------------------
1 | import { getDatabases } from "@/_utils/Mongodb";
2 | import { NextResponse } from "next/server";
3 | import { ObjectId } from "mongodb";
4 |
5 | export async function GET(request, { params }) {
6 | try {
7 | const { blogsCollection } = await getDatabases();
8 | const id = params.id;
9 |
10 | // Check if ID is valid ObjectId
11 | if (!ObjectId.isValid(id)) {
12 | console.error("Invalid blog ID:", id);
13 | return NextResponse.json({ error: "Invalid blog ID" }, { status: 400 });
14 | }
15 |
16 | // Fetch the blog post by ID
17 | const blogPost = await blogsCollection.findOne({ _id: new ObjectId(id) });
18 | console.log("Blog post:", blogPost);
19 |
20 | if (!blogPost) {
21 | return NextResponse.json(
22 | { error: "Blog post not found" },
23 | { status: 404 }
24 | );
25 | }
26 |
27 | return NextResponse.json(blogPost, { status: 200 });
28 | } catch (error) {
29 | console.error("Error fetching blog post:", error);
30 | return NextResponse.json(
31 | { error: "Failed to fetch blog post" },
32 | { status: 500 }
33 | );
34 | }
35 | }
36 |
37 | export async function DELETE(request, { params }) {
38 | try {
39 | const { blogsCollection } = await getDatabases();
40 | const id = params.id;
41 |
42 | // Check if ID is valid ObjectId
43 | if (!ObjectId.isValid(id)) {
44 | console.error("Invalid blog ID:", id);
45 | return NextResponse.json({ error: "Invalid blog ID" }, { status: 400 });
46 | }
47 |
48 | // Delete the blog post by ID
49 | const result = await blogsCollection.deleteOne({ _id: new ObjectId(id) });
50 |
51 | if (result.deletedCount === 0) {
52 | return NextResponse.json(
53 | { error: "Blog post not found" },
54 | { status: 404 }
55 | );
56 | }
57 |
58 | return NextResponse.json(
59 | { message: "Blog post deleted successfully" },
60 | { status: 200 }
61 | );
62 | } catch (error) {
63 | console.error("Error deleting blog post:", error);
64 | return NextResponse.json(
65 | { error: "Failed to delete blog post" },
66 | { status: 500 }
67 | );
68 | }
69 | }
70 |
71 | export async function PATCH(request, { params }) {
72 | try {
73 | const { blogsCollection } = await getDatabases();
74 | const id = params.id;
75 |
76 | // Check if ID is valid ObjectId
77 | if (!ObjectId.isValid(id)) {
78 | console.error("Invalid blog ID:", id);
79 | return NextResponse.json({ error: "Invalid blog ID" }, { status: 400 });
80 | }
81 |
82 | const data = await request.json();
83 |
84 | // Update the blog post by ID
85 | const result = await blogsCollection.updateOne(
86 | { _id: new ObjectId(id) },
87 | { $set: data }
88 | );
89 |
90 | if (result.modifiedCount === 0) {
91 | return NextResponse.json(
92 | { error: "Blog post not found or no changes made" },
93 | { status: 404 }
94 | );
95 | }
96 |
97 | return NextResponse.json(
98 | { message: "Blog post updated successfully" },
99 | { status: 200 }
100 | );
101 | } catch (error) {
102 | console.error("Error updating blog post:", error);
103 | return NextResponse.json(
104 | { error: "Failed to update blog post" },
105 | { status: 500 }
106 | );
107 | }
108 | }
109 |
110 | export async function POST(request, { params }) {
111 | try {
112 | // Input validation - ensure ID is provided
113 | const id = params.id;
114 | if (!id) {
115 | return NextResponse.json(
116 | { error: "Blog ID is required" },
117 | { status: 400 }
118 | );
119 | }
120 |
121 | // Validate ObjectId format
122 | if (!ObjectId.isValid(id)) {
123 | console.error("Invalid blog ID format:", id);
124 | return NextResponse.json(
125 | { error: "Invalid blog ID format" },
126 | { status: 400 }
127 | );
128 | }
129 |
130 | // Get database connection
131 | const { blogsCollection } = await getDatabases();
132 |
133 | // Retrieve current blog to get current views
134 | const blog = await blogsCollection.findOne({ _id: new ObjectId(id) });
135 | if (!blog) {
136 | return NextResponse.json(
137 | { error: "Blog post not found" },
138 | { status: 404 }
139 | );
140 | }
141 |
142 | // Ensure views is a number (default to 0 if not present)
143 | const currentViews = typeof blog.views === "number" ? blog.views : 0;
144 |
145 | // Use atomic update operation with $inc for better concurrency handling
146 | const result = await blogsCollection.updateOne(
147 | { _id: new ObjectId(id) },
148 | { $inc: { views: 1 } } // Increment views by 1
149 | );
150 |
151 | // Check if document was updated
152 | if (result.matchedCount === 0) {
153 | return NextResponse.json(
154 | { error: "Blog post not found" },
155 | { status: 404 }
156 | );
157 | }
158 |
159 | // Success response with previous and new view count
160 | return NextResponse.json(
161 | {
162 | message: "Blog view counted",
163 | previousViews: currentViews,
164 | currentViews: currentViews + 1,
165 | },
166 | { status: 200 }
167 | );
168 | } catch (error) {
169 | // Enhanced error logging
170 | console.error("Error updating blog view count:", error);
171 |
172 | // Structured error response
173 | return NextResponse.json(
174 | {
175 | error: "Failed to update blog post views",
176 | message: error.message,
177 | },
178 | { status: 500 }
179 | );
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/components/RegisterForm.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { useRouter } from "next/navigation";
5 | import Link from "next/link";
6 |
7 | export default function RegisterForm() {
8 | const [formData, setFormData] = useState({
9 | name: "",
10 | email: "",
11 | password: "",
12 | confirmPassword: "",
13 | });
14 | const [error, setError] = useState("");
15 | const [loading, setLoading] = useState(false);
16 | const router = useRouter();
17 |
18 | const handleChange = (e) => {
19 | const { name, value } = e.target;
20 | setFormData((prev) => ({
21 | ...prev,
22 | [name]: value,
23 | }));
24 | };
25 |
26 | const handleSubmit = async (e) => {
27 | e.preventDefault();
28 | setError("");
29 |
30 | // Basic validation
31 | if (formData.password !== formData.confirmPassword) {
32 | setError("Passwords do not match");
33 | return;
34 | }
35 |
36 | setLoading(true);
37 |
38 | try {
39 | // Simulate a brief loading delay for better user experience
40 | await new Promise((resolve) => setTimeout(resolve, 800));
41 |
42 | console.log("Auto-registration successful - public access granted");
43 |
44 | // Always redirect to dashboard - no real authentication
45 | router.push("/dashboard");
46 | } catch (err) {
47 | console.error("Error:", err);
48 | setError("An error occurred. Please try again.");
49 | } finally {
50 | setLoading(false);
51 | }
52 | };
53 |
54 | return (
55 |
56 |
57 | Create an Account
58 |
59 |
60 | {error && (
61 |
{error}
62 | )}
63 |
64 |
147 |
148 |
149 |
150 | Already have an account?{" "}
151 |
155 | Login here
156 |
157 |
158 |
159 | {/* Direct access link for user convenience */}
160 |