init
This commit is contained in:
commit
4df1d9f9a6
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
67
.vite/deps/_metadata.json
Normal file
67
.vite/deps/_metadata.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"hash": "9743482f",
|
||||
"configHash": "7878e186",
|
||||
"lockfileHash": "83b39f38",
|
||||
"browserHash": "76788ea6",
|
||||
"optimized": {
|
||||
"react/jsx-dev-runtime": {
|
||||
"src": "../../node_modules/react/jsx-dev-runtime.js",
|
||||
"file": "react_jsx-dev-runtime.js",
|
||||
"fileHash": "d2b38775",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react": {
|
||||
"src": "../../node_modules/react/index.js",
|
||||
"file": "react.js",
|
||||
"fileHash": "6f4e7327",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-dom/client": {
|
||||
"src": "../../node_modules/react-dom/client.js",
|
||||
"file": "react-dom_client.js",
|
||||
"fileHash": "f6bc709e",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-router-dom": {
|
||||
"src": "../../node_modules/react-router-dom/dist/index.mjs",
|
||||
"file": "react-router-dom.js",
|
||||
"fileHash": "d36ae5ff",
|
||||
"needsInterop": false
|
||||
},
|
||||
"react-markdown": {
|
||||
"src": "../../node_modules/react-markdown/index.js",
|
||||
"file": "react-markdown.js",
|
||||
"fileHash": "e6efcf45",
|
||||
"needsInterop": false
|
||||
},
|
||||
"gray-matter": {
|
||||
"src": "../../node_modules/gray-matter/index.js",
|
||||
"file": "gray-matter.js",
|
||||
"fileHash": "81140a6b",
|
||||
"needsInterop": true
|
||||
},
|
||||
"buffer": {
|
||||
"src": "../../node_modules/buffer/index.js",
|
||||
"file": "buffer.js",
|
||||
"fileHash": "e525b9e1",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-icons/fa": {
|
||||
"src": "../../node_modules/react-icons/fa/index.mjs",
|
||||
"file": "react-icons_fa.js",
|
||||
"fileHash": "f607209f",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
"chunk-YEN5GRII": {
|
||||
"file": "chunk-YEN5GRII.js"
|
||||
},
|
||||
"chunk-52VOLQIH": {
|
||||
"file": "chunk-52VOLQIH.js"
|
||||
},
|
||||
"chunk-2TUXWMP5": {
|
||||
"file": "chunk-2TUXWMP5.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
1793
.vite/deps/buffer.js
Normal file
1793
.vite/deps/buffer.js
Normal file
File diff suppressed because it is too large
Load Diff
7
.vite/deps/buffer.js.map
Normal file
7
.vite/deps/buffer.js.map
Normal file
File diff suppressed because one or more lines are too long
45
.vite/deps/chunk-2TUXWMP5.js
Normal file
45
.vite/deps/chunk-2TUXWMP5.js
Normal file
@ -0,0 +1,45 @@
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
||||
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
||||
}) : x)(function(x) {
|
||||
if (typeof require !== "undefined") return require.apply(this, arguments);
|
||||
throw Error('Dynamic require of "' + x + '" is not supported');
|
||||
});
|
||||
var __commonJS = (cb, mod) => function __require2() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
|
||||
export {
|
||||
__require,
|
||||
__commonJS,
|
||||
__export,
|
||||
__toESM,
|
||||
__publicField
|
||||
};
|
||||
7
.vite/deps/chunk-2TUXWMP5.js.map
Normal file
7
.vite/deps/chunk-2TUXWMP5.js.map
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
1139
.vite/deps/chunk-52VOLQIH.js
Normal file
1139
.vite/deps/chunk-52VOLQIH.js
Normal file
File diff suppressed because it is too large
Load Diff
7
.vite/deps/chunk-52VOLQIH.js.map
Normal file
7
.vite/deps/chunk-52VOLQIH.js.map
Normal file
File diff suppressed because one or more lines are too long
281
.vite/deps/chunk-YEN5GRII.js
Normal file
281
.vite/deps/chunk-YEN5GRII.js
Normal file
@ -0,0 +1,281 @@
|
||||
import {
|
||||
require_react
|
||||
} from "./chunk-52VOLQIH.js";
|
||||
import {
|
||||
__commonJS
|
||||
} from "./chunk-2TUXWMP5.js";
|
||||
|
||||
// node_modules/react-dom/cjs/react-dom.development.js
|
||||
var require_react_dom_development = __commonJS({
|
||||
"node_modules/react-dom/cjs/react-dom.development.js"(exports) {
|
||||
"use strict";
|
||||
(function() {
|
||||
function noop() {
|
||||
}
|
||||
function testStringCoercion(value) {
|
||||
return "" + value;
|
||||
}
|
||||
function createPortal$1(children, containerInfo, implementation) {
|
||||
var key = 3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;
|
||||
try {
|
||||
testStringCoercion(key);
|
||||
var JSCompiler_inline_result = false;
|
||||
} catch (e) {
|
||||
JSCompiler_inline_result = true;
|
||||
}
|
||||
JSCompiler_inline_result && (console.error(
|
||||
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
|
||||
"function" === typeof Symbol && Symbol.toStringTag && key[Symbol.toStringTag] || key.constructor.name || "Object"
|
||||
), testStringCoercion(key));
|
||||
return {
|
||||
$$typeof: REACT_PORTAL_TYPE,
|
||||
key: null == key ? null : "" + key,
|
||||
children,
|
||||
containerInfo,
|
||||
implementation
|
||||
};
|
||||
}
|
||||
function getCrossOriginStringAs(as, input) {
|
||||
if ("font" === as) return "";
|
||||
if ("string" === typeof input)
|
||||
return "use-credentials" === input ? input : "";
|
||||
}
|
||||
function getValueDescriptorExpectingObjectForWarning(thing) {
|
||||
return null === thing ? "`null`" : void 0 === thing ? "`undefined`" : "" === thing ? "an empty string" : 'something with type "' + typeof thing + '"';
|
||||
}
|
||||
function getValueDescriptorExpectingEnumForWarning(thing) {
|
||||
return null === thing ? "`null`" : void 0 === thing ? "`undefined`" : "" === thing ? "an empty string" : "string" === typeof thing ? JSON.stringify(thing) : "number" === typeof thing ? "`" + thing + "`" : 'something with type "' + typeof thing + '"';
|
||||
}
|
||||
function resolveDispatcher() {
|
||||
var dispatcher = ReactSharedInternals.H;
|
||||
null === dispatcher && console.error(
|
||||
"Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem."
|
||||
);
|
||||
return dispatcher;
|
||||
}
|
||||
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
|
||||
var React = require_react(), Internals = {
|
||||
d: {
|
||||
f: noop,
|
||||
r: function() {
|
||||
throw Error(
|
||||
"Invalid form element. requestFormReset must be passed a form that was rendered by React."
|
||||
);
|
||||
},
|
||||
D: noop,
|
||||
C: noop,
|
||||
L: noop,
|
||||
m: noop,
|
||||
X: noop,
|
||||
S: noop,
|
||||
M: noop
|
||||
},
|
||||
p: 0,
|
||||
findDOMNode: null
|
||||
}, REACT_PORTAL_TYPE = Symbol.for("react.portal"), ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
||||
"function" === typeof Map && null != Map.prototype && "function" === typeof Map.prototype.forEach && "function" === typeof Set && null != Set.prototype && "function" === typeof Set.prototype.clear && "function" === typeof Set.prototype.forEach || console.error(
|
||||
"React depends on Map and Set built-in types. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"
|
||||
);
|
||||
exports.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = Internals;
|
||||
exports.createPortal = function(children, container) {
|
||||
var key = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null;
|
||||
if (!container || 1 !== container.nodeType && 9 !== container.nodeType && 11 !== container.nodeType)
|
||||
throw Error("Target container is not a DOM element.");
|
||||
return createPortal$1(children, container, null, key);
|
||||
};
|
||||
exports.flushSync = function(fn) {
|
||||
var previousTransition = ReactSharedInternals.T, previousUpdatePriority = Internals.p;
|
||||
try {
|
||||
if (ReactSharedInternals.T = null, Internals.p = 2, fn)
|
||||
return fn();
|
||||
} finally {
|
||||
ReactSharedInternals.T = previousTransition, Internals.p = previousUpdatePriority, Internals.d.f() && console.error(
|
||||
"flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task."
|
||||
);
|
||||
}
|
||||
};
|
||||
exports.preconnect = function(href, options) {
|
||||
"string" === typeof href && href ? null != options && "object" !== typeof options ? console.error(
|
||||
"ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.",
|
||||
getValueDescriptorExpectingEnumForWarning(options)
|
||||
) : null != options && "string" !== typeof options.crossOrigin && console.error(
|
||||
"ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.",
|
||||
getValueDescriptorExpectingObjectForWarning(options.crossOrigin)
|
||||
) : console.error(
|
||||
"ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.",
|
||||
getValueDescriptorExpectingObjectForWarning(href)
|
||||
);
|
||||
"string" === typeof href && (options ? (options = options.crossOrigin, options = "string" === typeof options ? "use-credentials" === options ? options : "" : void 0) : options = null, Internals.d.C(href, options));
|
||||
};
|
||||
exports.prefetchDNS = function(href) {
|
||||
if ("string" !== typeof href || !href)
|
||||
console.error(
|
||||
"ReactDOM.prefetchDNS(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.",
|
||||
getValueDescriptorExpectingObjectForWarning(href)
|
||||
);
|
||||
else if (1 < arguments.length) {
|
||||
var options = arguments[1];
|
||||
"object" === typeof options && options.hasOwnProperty("crossOrigin") ? console.error(
|
||||
"ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.",
|
||||
getValueDescriptorExpectingEnumForWarning(options)
|
||||
) : console.error(
|
||||
"ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.",
|
||||
getValueDescriptorExpectingEnumForWarning(options)
|
||||
);
|
||||
}
|
||||
"string" === typeof href && Internals.d.D(href);
|
||||
};
|
||||
exports.preinit = function(href, options) {
|
||||
"string" === typeof href && href ? null == options || "object" !== typeof options ? console.error(
|
||||
"ReactDOM.preinit(): Expected the `options` argument (second) to be an object with an `as` property describing the type of resource to be preinitialized but encountered %s instead.",
|
||||
getValueDescriptorExpectingEnumForWarning(options)
|
||||
) : "style" !== options.as && "script" !== options.as && console.error(
|
||||
'ReactDOM.preinit(): Expected the `as` property in the `options` argument (second) to contain a valid value describing the type of resource to be preinitialized but encountered %s instead. Valid values for `as` are "style" and "script".',
|
||||
getValueDescriptorExpectingEnumForWarning(options.as)
|
||||
) : console.error(
|
||||
"ReactDOM.preinit(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.",
|
||||
getValueDescriptorExpectingObjectForWarning(href)
|
||||
);
|
||||
if ("string" === typeof href && options && "string" === typeof options.as) {
|
||||
var as = options.as, crossOrigin = getCrossOriginStringAs(as, options.crossOrigin), integrity = "string" === typeof options.integrity ? options.integrity : void 0, fetchPriority = "string" === typeof options.fetchPriority ? options.fetchPriority : void 0;
|
||||
"style" === as ? Internals.d.S(
|
||||
href,
|
||||
"string" === typeof options.precedence ? options.precedence : void 0,
|
||||
{
|
||||
crossOrigin,
|
||||
integrity,
|
||||
fetchPriority
|
||||
}
|
||||
) : "script" === as && Internals.d.X(href, {
|
||||
crossOrigin,
|
||||
integrity,
|
||||
fetchPriority,
|
||||
nonce: "string" === typeof options.nonce ? options.nonce : void 0
|
||||
});
|
||||
}
|
||||
};
|
||||
exports.preinitModule = function(href, options) {
|
||||
var encountered = "";
|
||||
"string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
|
||||
void 0 !== options && "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : options && "as" in options && "script" !== options.as && (encountered += " The `as` option encountered was " + getValueDescriptorExpectingEnumForWarning(options.as) + ".");
|
||||
if (encountered)
|
||||
console.error(
|
||||
"ReactDOM.preinitModule(): Expected up to two arguments, a non-empty `href` string and, optionally, an `options` object with a valid `as` property.%s",
|
||||
encountered
|
||||
);
|
||||
else
|
||||
switch (encountered = options && "string" === typeof options.as ? options.as : "script", encountered) {
|
||||
case "script":
|
||||
break;
|
||||
default:
|
||||
encountered = getValueDescriptorExpectingEnumForWarning(encountered), console.error(
|
||||
'ReactDOM.preinitModule(): Currently the only supported "as" type for this function is "script" but received "%s" instead. This warning was generated for `href` "%s". In the future other module types will be supported, aligning with the import-attributes proposal. Learn more here: (https://github.com/tc39/proposal-import-attributes)',
|
||||
encountered,
|
||||
href
|
||||
);
|
||||
}
|
||||
if ("string" === typeof href)
|
||||
if ("object" === typeof options && null !== options) {
|
||||
if (null == options.as || "script" === options.as)
|
||||
encountered = getCrossOriginStringAs(
|
||||
options.as,
|
||||
options.crossOrigin
|
||||
), Internals.d.M(href, {
|
||||
crossOrigin: encountered,
|
||||
integrity: "string" === typeof options.integrity ? options.integrity : void 0,
|
||||
nonce: "string" === typeof options.nonce ? options.nonce : void 0
|
||||
});
|
||||
} else null == options && Internals.d.M(href);
|
||||
};
|
||||
exports.preload = function(href, options) {
|
||||
var encountered = "";
|
||||
"string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
|
||||
null == options || "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : "string" === typeof options.as && options.as || (encountered += " The `as` option encountered was " + getValueDescriptorExpectingObjectForWarning(options.as) + ".");
|
||||
encountered && console.error(
|
||||
'ReactDOM.preload(): Expected two arguments, a non-empty `href` string and an `options` object with an `as` property valid for a `<link rel="preload" as="..." />` tag.%s',
|
||||
encountered
|
||||
);
|
||||
if ("string" === typeof href && "object" === typeof options && null !== options && "string" === typeof options.as) {
|
||||
encountered = options.as;
|
||||
var crossOrigin = getCrossOriginStringAs(
|
||||
encountered,
|
||||
options.crossOrigin
|
||||
);
|
||||
Internals.d.L(href, encountered, {
|
||||
crossOrigin,
|
||||
integrity: "string" === typeof options.integrity ? options.integrity : void 0,
|
||||
nonce: "string" === typeof options.nonce ? options.nonce : void 0,
|
||||
type: "string" === typeof options.type ? options.type : void 0,
|
||||
fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0,
|
||||
referrerPolicy: "string" === typeof options.referrerPolicy ? options.referrerPolicy : void 0,
|
||||
imageSrcSet: "string" === typeof options.imageSrcSet ? options.imageSrcSet : void 0,
|
||||
imageSizes: "string" === typeof options.imageSizes ? options.imageSizes : void 0,
|
||||
media: "string" === typeof options.media ? options.media : void 0
|
||||
});
|
||||
}
|
||||
};
|
||||
exports.preloadModule = function(href, options) {
|
||||
var encountered = "";
|
||||
"string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
|
||||
void 0 !== options && "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : options && "as" in options && "string" !== typeof options.as && (encountered += " The `as` option encountered was " + getValueDescriptorExpectingObjectForWarning(options.as) + ".");
|
||||
encountered && console.error(
|
||||
'ReactDOM.preloadModule(): Expected two arguments, a non-empty `href` string and, optionally, an `options` object with an `as` property valid for a `<link rel="modulepreload" as="..." />` tag.%s',
|
||||
encountered
|
||||
);
|
||||
"string" === typeof href && (options ? (encountered = getCrossOriginStringAs(
|
||||
options.as,
|
||||
options.crossOrigin
|
||||
), Internals.d.m(href, {
|
||||
as: "string" === typeof options.as && "script" !== options.as ? options.as : void 0,
|
||||
crossOrigin: encountered,
|
||||
integrity: "string" === typeof options.integrity ? options.integrity : void 0
|
||||
})) : Internals.d.m(href));
|
||||
};
|
||||
exports.requestFormReset = function(form) {
|
||||
Internals.d.r(form);
|
||||
};
|
||||
exports.unstable_batchedUpdates = function(fn, a) {
|
||||
return fn(a);
|
||||
};
|
||||
exports.useFormState = function(action, initialState, permalink) {
|
||||
return resolveDispatcher().useFormState(action, initialState, permalink);
|
||||
};
|
||||
exports.useFormStatus = function() {
|
||||
return resolveDispatcher().useHostTransitionStatus();
|
||||
};
|
||||
exports.version = "19.0.0";
|
||||
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/react-dom/index.js
|
||||
var require_react_dom = __commonJS({
|
||||
"node_modules/react-dom/index.js"(exports, module) {
|
||||
"use strict";
|
||||
if (false) {
|
||||
checkDCE();
|
||||
module.exports = null;
|
||||
} else {
|
||||
module.exports = require_react_dom_development();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export {
|
||||
require_react_dom
|
||||
};
|
||||
/*! Bundled license information:
|
||||
|
||||
react-dom/cjs/react-dom.development.js:
|
||||
(**
|
||||
* @license React
|
||||
* react-dom.development.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
*/
|
||||
//# sourceMappingURL=chunk-YEN5GRII.js.map
|
||||
7
.vite/deps/chunk-YEN5GRII.js.map
Normal file
7
.vite/deps/chunk-YEN5GRII.js.map
Normal file
File diff suppressed because one or more lines are too long
3507
.vite/deps/gray-matter.js
Normal file
3507
.vite/deps/gray-matter.js
Normal file
File diff suppressed because it is too large
Load Diff
7
.vite/deps/gray-matter.js.map
Normal file
7
.vite/deps/gray-matter.js.map
Normal file
File diff suppressed because one or more lines are too long
3
.vite/deps/package.json
Normal file
3
.vite/deps/package.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
17917
.vite/deps/react-dom_client.js
vendored
Normal file
17917
.vite/deps/react-dom_client.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
.vite/deps/react-dom_client.js.map
Normal file
7
.vite/deps/react-dom_client.js.map
Normal file
File diff suppressed because one or more lines are too long
6593
.vite/deps/react-icons_fa.js
vendored
Normal file
6593
.vite/deps/react-icons_fa.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
.vite/deps/react-icons_fa.js.map
Normal file
7
.vite/deps/react-icons_fa.js.map
Normal file
File diff suppressed because one or more lines are too long
12110
.vite/deps/react-markdown.js
vendored
Normal file
12110
.vite/deps/react-markdown.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
.vite/deps/react-markdown.js.map
Normal file
7
.vite/deps/react-markdown.js.map
Normal file
File diff suppressed because one or more lines are too long
11494
.vite/deps/react-router-dom.js
vendored
Normal file
11494
.vite/deps/react-router-dom.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
.vite/deps/react-router-dom.js.map
Normal file
7
.vite/deps/react-router-dom.js.map
Normal file
File diff suppressed because one or more lines are too long
5
.vite/deps/react.js
vendored
Normal file
5
.vite/deps/react.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import {
|
||||
require_react
|
||||
} from "./chunk-52VOLQIH.js";
|
||||
import "./chunk-2TUXWMP5.js";
|
||||
export default require_react();
|
||||
7
.vite/deps/react.js.map
Normal file
7
.vite/deps/react.js.map
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
470
.vite/deps/react_jsx-dev-runtime.js
Normal file
470
.vite/deps/react_jsx-dev-runtime.js
Normal file
@ -0,0 +1,470 @@
|
||||
import {
|
||||
require_react
|
||||
} from "./chunk-52VOLQIH.js";
|
||||
import {
|
||||
__commonJS
|
||||
} from "./chunk-2TUXWMP5.js";
|
||||
|
||||
// node_modules/react/cjs/react-jsx-dev-runtime.development.js
|
||||
var require_react_jsx_dev_runtime_development = __commonJS({
|
||||
"node_modules/react/cjs/react-jsx-dev-runtime.development.js"(exports) {
|
||||
"use strict";
|
||||
(function() {
|
||||
function getComponentNameFromType(type) {
|
||||
if (null == type) return null;
|
||||
if ("function" === typeof type)
|
||||
return type.$$typeof === REACT_CLIENT_REFERENCE$2 ? null : type.displayName || type.name || null;
|
||||
if ("string" === typeof type) return type;
|
||||
switch (type) {
|
||||
case REACT_FRAGMENT_TYPE:
|
||||
return "Fragment";
|
||||
case REACT_PORTAL_TYPE:
|
||||
return "Portal";
|
||||
case REACT_PROFILER_TYPE:
|
||||
return "Profiler";
|
||||
case REACT_STRICT_MODE_TYPE:
|
||||
return "StrictMode";
|
||||
case REACT_SUSPENSE_TYPE:
|
||||
return "Suspense";
|
||||
case REACT_SUSPENSE_LIST_TYPE:
|
||||
return "SuspenseList";
|
||||
}
|
||||
if ("object" === typeof type)
|
||||
switch ("number" === typeof type.tag && console.error(
|
||||
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
|
||||
), type.$$typeof) {
|
||||
case REACT_CONTEXT_TYPE:
|
||||
return (type.displayName || "Context") + ".Provider";
|
||||
case REACT_CONSUMER_TYPE:
|
||||
return (type._context.displayName || "Context") + ".Consumer";
|
||||
case REACT_FORWARD_REF_TYPE:
|
||||
var innerType = type.render;
|
||||
type = type.displayName;
|
||||
type || (type = innerType.displayName || innerType.name || "", type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef");
|
||||
return type;
|
||||
case REACT_MEMO_TYPE:
|
||||
return innerType = type.displayName || null, null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo";
|
||||
case REACT_LAZY_TYPE:
|
||||
innerType = type._payload;
|
||||
type = type._init;
|
||||
try {
|
||||
return getComponentNameFromType(type(innerType));
|
||||
} catch (x) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function testStringCoercion(value) {
|
||||
return "" + value;
|
||||
}
|
||||
function checkKeyStringCoercion(value) {
|
||||
try {
|
||||
testStringCoercion(value);
|
||||
var JSCompiler_inline_result = false;
|
||||
} catch (e) {
|
||||
JSCompiler_inline_result = true;
|
||||
}
|
||||
if (JSCompiler_inline_result) {
|
||||
JSCompiler_inline_result = console;
|
||||
var JSCompiler_temp_const = JSCompiler_inline_result.error;
|
||||
var JSCompiler_inline_result$jscomp$0 = "function" === typeof Symbol && Symbol.toStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
|
||||
JSCompiler_temp_const.call(
|
||||
JSCompiler_inline_result,
|
||||
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
|
||||
JSCompiler_inline_result$jscomp$0
|
||||
);
|
||||
return testStringCoercion(value);
|
||||
}
|
||||
}
|
||||
function disabledLog() {
|
||||
}
|
||||
function disableLogs() {
|
||||
if (0 === disabledDepth) {
|
||||
prevLog = console.log;
|
||||
prevInfo = console.info;
|
||||
prevWarn = console.warn;
|
||||
prevError = console.error;
|
||||
prevGroup = console.group;
|
||||
prevGroupCollapsed = console.groupCollapsed;
|
||||
prevGroupEnd = console.groupEnd;
|
||||
var props = {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: disabledLog,
|
||||
writable: true
|
||||
};
|
||||
Object.defineProperties(console, {
|
||||
info: props,
|
||||
log: props,
|
||||
warn: props,
|
||||
error: props,
|
||||
group: props,
|
||||
groupCollapsed: props,
|
||||
groupEnd: props
|
||||
});
|
||||
}
|
||||
disabledDepth++;
|
||||
}
|
||||
function reenableLogs() {
|
||||
disabledDepth--;
|
||||
if (0 === disabledDepth) {
|
||||
var props = { configurable: true, enumerable: true, writable: true };
|
||||
Object.defineProperties(console, {
|
||||
log: assign({}, props, { value: prevLog }),
|
||||
info: assign({}, props, { value: prevInfo }),
|
||||
warn: assign({}, props, { value: prevWarn }),
|
||||
error: assign({}, props, { value: prevError }),
|
||||
group: assign({}, props, { value: prevGroup }),
|
||||
groupCollapsed: assign({}, props, { value: prevGroupCollapsed }),
|
||||
groupEnd: assign({}, props, { value: prevGroupEnd })
|
||||
});
|
||||
}
|
||||
0 > disabledDepth && console.error(
|
||||
"disabledDepth fell below zero. This is a bug in React. Please file an issue."
|
||||
);
|
||||
}
|
||||
function describeBuiltInComponentFrame(name) {
|
||||
if (void 0 === prefix)
|
||||
try {
|
||||
throw Error();
|
||||
} catch (x) {
|
||||
var match = x.stack.trim().match(/\n( *(at )?)/);
|
||||
prefix = match && match[1] || "";
|
||||
suffix = -1 < x.stack.indexOf("\n at") ? " (<anonymous>)" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : "";
|
||||
}
|
||||
return "\n" + prefix + name + suffix;
|
||||
}
|
||||
function describeNativeComponentFrame(fn, construct) {
|
||||
if (!fn || reentry) return "";
|
||||
var frame = componentFrameCache.get(fn);
|
||||
if (void 0 !== frame) return frame;
|
||||
reentry = true;
|
||||
frame = Error.prepareStackTrace;
|
||||
Error.prepareStackTrace = void 0;
|
||||
var previousDispatcher = null;
|
||||
previousDispatcher = ReactSharedInternals.H;
|
||||
ReactSharedInternals.H = null;
|
||||
disableLogs();
|
||||
try {
|
||||
var RunInRootFrame = {
|
||||
DetermineComponentFrameRoot: function() {
|
||||
try {
|
||||
if (construct) {
|
||||
var Fake = function() {
|
||||
throw Error();
|
||||
};
|
||||
Object.defineProperty(Fake.prototype, "props", {
|
||||
set: function() {
|
||||
throw Error();
|
||||
}
|
||||
});
|
||||
if ("object" === typeof Reflect && Reflect.construct) {
|
||||
try {
|
||||
Reflect.construct(Fake, []);
|
||||
} catch (x) {
|
||||
var control = x;
|
||||
}
|
||||
Reflect.construct(fn, [], Fake);
|
||||
} else {
|
||||
try {
|
||||
Fake.call();
|
||||
} catch (x$0) {
|
||||
control = x$0;
|
||||
}
|
||||
fn.call(Fake.prototype);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
throw Error();
|
||||
} catch (x$1) {
|
||||
control = x$1;
|
||||
}
|
||||
(Fake = fn()) && "function" === typeof Fake.catch && Fake.catch(function() {
|
||||
});
|
||||
}
|
||||
} catch (sample) {
|
||||
if (sample && control && "string" === typeof sample.stack)
|
||||
return [sample.stack, control.stack];
|
||||
}
|
||||
return [null, null];
|
||||
}
|
||||
};
|
||||
RunInRootFrame.DetermineComponentFrameRoot.displayName = "DetermineComponentFrameRoot";
|
||||
var namePropDescriptor = Object.getOwnPropertyDescriptor(
|
||||
RunInRootFrame.DetermineComponentFrameRoot,
|
||||
"name"
|
||||
);
|
||||
namePropDescriptor && namePropDescriptor.configurable && Object.defineProperty(
|
||||
RunInRootFrame.DetermineComponentFrameRoot,
|
||||
"name",
|
||||
{ value: "DetermineComponentFrameRoot" }
|
||||
);
|
||||
var _RunInRootFrame$Deter = RunInRootFrame.DetermineComponentFrameRoot(), sampleStack = _RunInRootFrame$Deter[0], controlStack = _RunInRootFrame$Deter[1];
|
||||
if (sampleStack && controlStack) {
|
||||
var sampleLines = sampleStack.split("\n"), controlLines = controlStack.split("\n");
|
||||
for (_RunInRootFrame$Deter = namePropDescriptor = 0; namePropDescriptor < sampleLines.length && !sampleLines[namePropDescriptor].includes(
|
||||
"DetermineComponentFrameRoot"
|
||||
); )
|
||||
namePropDescriptor++;
|
||||
for (; _RunInRootFrame$Deter < controlLines.length && !controlLines[_RunInRootFrame$Deter].includes(
|
||||
"DetermineComponentFrameRoot"
|
||||
); )
|
||||
_RunInRootFrame$Deter++;
|
||||
if (namePropDescriptor === sampleLines.length || _RunInRootFrame$Deter === controlLines.length)
|
||||
for (namePropDescriptor = sampleLines.length - 1, _RunInRootFrame$Deter = controlLines.length - 1; 1 <= namePropDescriptor && 0 <= _RunInRootFrame$Deter && sampleLines[namePropDescriptor] !== controlLines[_RunInRootFrame$Deter]; )
|
||||
_RunInRootFrame$Deter--;
|
||||
for (; 1 <= namePropDescriptor && 0 <= _RunInRootFrame$Deter; namePropDescriptor--, _RunInRootFrame$Deter--)
|
||||
if (sampleLines[namePropDescriptor] !== controlLines[_RunInRootFrame$Deter]) {
|
||||
if (1 !== namePropDescriptor || 1 !== _RunInRootFrame$Deter) {
|
||||
do
|
||||
if (namePropDescriptor--, _RunInRootFrame$Deter--, 0 > _RunInRootFrame$Deter || sampleLines[namePropDescriptor] !== controlLines[_RunInRootFrame$Deter]) {
|
||||
var _frame = "\n" + sampleLines[namePropDescriptor].replace(
|
||||
" at new ",
|
||||
" at "
|
||||
);
|
||||
fn.displayName && _frame.includes("<anonymous>") && (_frame = _frame.replace("<anonymous>", fn.displayName));
|
||||
"function" === typeof fn && componentFrameCache.set(fn, _frame);
|
||||
return _frame;
|
||||
}
|
||||
while (1 <= namePropDescriptor && 0 <= _RunInRootFrame$Deter);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reentry = false, ReactSharedInternals.H = previousDispatcher, reenableLogs(), Error.prepareStackTrace = frame;
|
||||
}
|
||||
sampleLines = (sampleLines = fn ? fn.displayName || fn.name : "") ? describeBuiltInComponentFrame(sampleLines) : "";
|
||||
"function" === typeof fn && componentFrameCache.set(fn, sampleLines);
|
||||
return sampleLines;
|
||||
}
|
||||
function describeUnknownElementTypeFrameInDEV(type) {
|
||||
if (null == type) return "";
|
||||
if ("function" === typeof type) {
|
||||
var prototype = type.prototype;
|
||||
return describeNativeComponentFrame(
|
||||
type,
|
||||
!(!prototype || !prototype.isReactComponent)
|
||||
);
|
||||
}
|
||||
if ("string" === typeof type) return describeBuiltInComponentFrame(type);
|
||||
switch (type) {
|
||||
case REACT_SUSPENSE_TYPE:
|
||||
return describeBuiltInComponentFrame("Suspense");
|
||||
case REACT_SUSPENSE_LIST_TYPE:
|
||||
return describeBuiltInComponentFrame("SuspenseList");
|
||||
}
|
||||
if ("object" === typeof type)
|
||||
switch (type.$$typeof) {
|
||||
case REACT_FORWARD_REF_TYPE:
|
||||
return type = describeNativeComponentFrame(type.render, false), type;
|
||||
case REACT_MEMO_TYPE:
|
||||
return describeUnknownElementTypeFrameInDEV(type.type);
|
||||
case REACT_LAZY_TYPE:
|
||||
prototype = type._payload;
|
||||
type = type._init;
|
||||
try {
|
||||
return describeUnknownElementTypeFrameInDEV(type(prototype));
|
||||
} catch (x) {
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function getOwner() {
|
||||
var dispatcher = ReactSharedInternals.A;
|
||||
return null === dispatcher ? null : dispatcher.getOwner();
|
||||
}
|
||||
function hasValidKey(config) {
|
||||
if (hasOwnProperty.call(config, "key")) {
|
||||
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
|
||||
if (getter && getter.isReactWarning) return false;
|
||||
}
|
||||
return void 0 !== config.key;
|
||||
}
|
||||
function defineKeyPropWarningGetter(props, displayName) {
|
||||
function warnAboutAccessingKey() {
|
||||
specialPropKeyWarningShown || (specialPropKeyWarningShown = true, console.error(
|
||||
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
|
||||
displayName
|
||||
));
|
||||
}
|
||||
warnAboutAccessingKey.isReactWarning = true;
|
||||
Object.defineProperty(props, "key", {
|
||||
get: warnAboutAccessingKey,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
function elementRefGetterWithDeprecationWarning() {
|
||||
var componentName = getComponentNameFromType(this.type);
|
||||
didWarnAboutElementRef[componentName] || (didWarnAboutElementRef[componentName] = true, console.error(
|
||||
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
|
||||
));
|
||||
componentName = this.props.ref;
|
||||
return void 0 !== componentName ? componentName : null;
|
||||
}
|
||||
function ReactElement(type, key, self, source, owner, props) {
|
||||
self = props.ref;
|
||||
type = {
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
type,
|
||||
key,
|
||||
props,
|
||||
_owner: owner
|
||||
};
|
||||
null !== (void 0 !== self ? self : null) ? Object.defineProperty(type, "ref", {
|
||||
enumerable: false,
|
||||
get: elementRefGetterWithDeprecationWarning
|
||||
}) : Object.defineProperty(type, "ref", { enumerable: false, value: null });
|
||||
type._store = {};
|
||||
Object.defineProperty(type._store, "validated", {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: 0
|
||||
});
|
||||
Object.defineProperty(type, "_debugInfo", {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: null
|
||||
});
|
||||
Object.freeze && (Object.freeze(type.props), Object.freeze(type));
|
||||
return type;
|
||||
}
|
||||
function jsxDEVImpl(type, config, maybeKey, isStaticChildren, source, self) {
|
||||
if ("string" === typeof type || "function" === typeof type || type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || type === REACT_OFFSCREEN_TYPE || "object" === typeof type && null !== type && (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_CONSUMER_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || type.$$typeof === REACT_CLIENT_REFERENCE$1 || void 0 !== type.getModuleId)) {
|
||||
var children = config.children;
|
||||
if (void 0 !== children)
|
||||
if (isStaticChildren)
|
||||
if (isArrayImpl(children)) {
|
||||
for (isStaticChildren = 0; isStaticChildren < children.length; isStaticChildren++)
|
||||
validateChildKeys(children[isStaticChildren], type);
|
||||
Object.freeze && Object.freeze(children);
|
||||
} else
|
||||
console.error(
|
||||
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
|
||||
);
|
||||
else validateChildKeys(children, type);
|
||||
} else {
|
||||
children = "";
|
||||
if (void 0 === type || "object" === typeof type && null !== type && 0 === Object.keys(type).length)
|
||||
children += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
|
||||
null === type ? isStaticChildren = "null" : isArrayImpl(type) ? isStaticChildren = "array" : void 0 !== type && type.$$typeof === REACT_ELEMENT_TYPE ? (isStaticChildren = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />", children = " Did you accidentally export a JSX literal instead of a component?") : isStaticChildren = typeof type;
|
||||
console.error(
|
||||
"React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",
|
||||
isStaticChildren,
|
||||
children
|
||||
);
|
||||
}
|
||||
if (hasOwnProperty.call(config, "key")) {
|
||||
children = getComponentNameFromType(type);
|
||||
var keys = Object.keys(config).filter(function(k) {
|
||||
return "key" !== k;
|
||||
});
|
||||
isStaticChildren = 0 < keys.length ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
|
||||
didWarnAboutKeySpread[children + isStaticChildren] || (keys = 0 < keys.length ? "{" + keys.join(": ..., ") + ": ...}" : "{}", console.error(
|
||||
'A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />',
|
||||
isStaticChildren,
|
||||
children,
|
||||
keys,
|
||||
children
|
||||
), didWarnAboutKeySpread[children + isStaticChildren] = true);
|
||||
}
|
||||
children = null;
|
||||
void 0 !== maybeKey && (checkKeyStringCoercion(maybeKey), children = "" + maybeKey);
|
||||
hasValidKey(config) && (checkKeyStringCoercion(config.key), children = "" + config.key);
|
||||
if ("key" in config) {
|
||||
maybeKey = {};
|
||||
for (var propName in config)
|
||||
"key" !== propName && (maybeKey[propName] = config[propName]);
|
||||
} else maybeKey = config;
|
||||
children && defineKeyPropWarningGetter(
|
||||
maybeKey,
|
||||
"function" === typeof type ? type.displayName || type.name || "Unknown" : type
|
||||
);
|
||||
return ReactElement(type, children, self, source, getOwner(), maybeKey);
|
||||
}
|
||||
function validateChildKeys(node, parentType) {
|
||||
if ("object" === typeof node && node && node.$$typeof !== REACT_CLIENT_REFERENCE) {
|
||||
if (isArrayImpl(node))
|
||||
for (var i = 0; i < node.length; i++) {
|
||||
var child = node[i];
|
||||
isValidElement(child) && validateExplicitKey(child, parentType);
|
||||
}
|
||||
else if (isValidElement(node))
|
||||
node._store && (node._store.validated = 1);
|
||||
else if (null === node || "object" !== typeof node ? i = null : (i = MAYBE_ITERATOR_SYMBOL && node[MAYBE_ITERATOR_SYMBOL] || node["@@iterator"], i = "function" === typeof i ? i : null), "function" === typeof i && i !== node.entries && (i = i.call(node), i !== node))
|
||||
for (; !(node = i.next()).done; )
|
||||
isValidElement(node.value) && validateExplicitKey(node.value, parentType);
|
||||
}
|
||||
}
|
||||
function isValidElement(object) {
|
||||
return "object" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE;
|
||||
}
|
||||
function validateExplicitKey(element, parentType) {
|
||||
if (element._store && !element._store.validated && null == element.key && (element._store.validated = 1, parentType = getCurrentComponentErrorInfo(parentType), !ownerHasKeyUseWarning[parentType])) {
|
||||
ownerHasKeyUseWarning[parentType] = true;
|
||||
var childOwner = "";
|
||||
element && null != element._owner && element._owner !== getOwner() && (childOwner = null, "number" === typeof element._owner.tag ? childOwner = getComponentNameFromType(element._owner.type) : "string" === typeof element._owner.name && (childOwner = element._owner.name), childOwner = " It was passed a child from " + childOwner + ".");
|
||||
var prevGetCurrentStack = ReactSharedInternals.getCurrentStack;
|
||||
ReactSharedInternals.getCurrentStack = function() {
|
||||
var stack = describeUnknownElementTypeFrameInDEV(element.type);
|
||||
prevGetCurrentStack && (stack += prevGetCurrentStack() || "");
|
||||
return stack;
|
||||
};
|
||||
console.error(
|
||||
'Each child in a list should have a unique "key" prop.%s%s See https://react.dev/link/warning-keys for more information.',
|
||||
parentType,
|
||||
childOwner
|
||||
);
|
||||
ReactSharedInternals.getCurrentStack = prevGetCurrentStack;
|
||||
}
|
||||
}
|
||||
function getCurrentComponentErrorInfo(parentType) {
|
||||
var info = "", owner = getOwner();
|
||||
owner && (owner = getComponentNameFromType(owner.type)) && (info = "\n\nCheck the render method of `" + owner + "`.");
|
||||
info || (parentType = getComponentNameFromType(parentType)) && (info = "\n\nCheck the top-level render call using <" + parentType + ">.");
|
||||
return info;
|
||||
}
|
||||
var React = require_react(), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler");
|
||||
Symbol.for("react.provider");
|
||||
var REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen"), MAYBE_ITERATOR_SYMBOL = Symbol.iterator, REACT_CLIENT_REFERENCE$2 = Symbol.for("react.client.reference"), ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, assign = Object.assign, REACT_CLIENT_REFERENCE$1 = Symbol.for("react.client.reference"), isArrayImpl = Array.isArray, disabledDepth = 0, prevLog, prevInfo, prevWarn, prevError, prevGroup, prevGroupCollapsed, prevGroupEnd;
|
||||
disabledLog.__reactDisabledLog = true;
|
||||
var prefix, suffix, reentry = false;
|
||||
var componentFrameCache = new ("function" === typeof WeakMap ? WeakMap : Map)();
|
||||
var REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), specialPropKeyWarningShown;
|
||||
var didWarnAboutElementRef = {};
|
||||
var didWarnAboutKeySpread = {}, ownerHasKeyUseWarning = {};
|
||||
exports.Fragment = REACT_FRAGMENT_TYPE;
|
||||
exports.jsxDEV = function(type, config, maybeKey, isStaticChildren, source, self) {
|
||||
return jsxDEVImpl(type, config, maybeKey, isStaticChildren, source, self);
|
||||
};
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/react/jsx-dev-runtime.js
|
||||
var require_jsx_dev_runtime = __commonJS({
|
||||
"node_modules/react/jsx-dev-runtime.js"(exports, module) {
|
||||
if (false) {
|
||||
module.exports = null;
|
||||
} else {
|
||||
module.exports = require_react_jsx_dev_runtime_development();
|
||||
}
|
||||
}
|
||||
});
|
||||
export default require_jsx_dev_runtime();
|
||||
/*! Bundled license information:
|
||||
|
||||
react/cjs/react-jsx-dev-runtime.development.js:
|
||||
(**
|
||||
* @license React
|
||||
* react-jsx-dev-runtime.development.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
*/
|
||||
//# sourceMappingURL=react_jsx-dev-runtime.js.map
|
||||
7
.vite/deps/react_jsx-dev-runtime.js.map
Normal file
7
.vite/deps/react_jsx-dev-runtime.js.map
Normal file
File diff suppressed because one or more lines are too long
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@ -0,0 +1,31 @@
|
||||
# Build stage
|
||||
FROM node:20-alpine as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy built assets from build stage
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy nginx configuration
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
# Start nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
54
README.md
Normal file
54
README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
extends: [
|
||||
// Remove ...tseslint.configs.recommended and replace with this
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
],
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default tseslint.config({
|
||||
plugins: {
|
||||
// Add the react-x and react-dom plugins
|
||||
'react-x': reactX,
|
||||
'react-dom': reactDom,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended typescript rules
|
||||
...reactX.configs['recommended-typescript'].rules,
|
||||
...reactDom.configs.recommended.rules,
|
||||
},
|
||||
})
|
||||
```
|
||||
16
backend/.env.example
Normal file
16
backend/.env.example
Normal file
@ -0,0 +1,16 @@
|
||||
# Server Configuration
|
||||
PORT=3001
|
||||
NODE_ENV=development
|
||||
|
||||
# Email Configuration
|
||||
SMTP_HOST=smtp.mail.me.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@me.com
|
||||
SMTP_PASS=your-app-specific-password
|
||||
SMTP_FROM=your-email@me.com
|
||||
SMTP_TO=your-email@me.com
|
||||
|
||||
# Security
|
||||
CORS_ORIGIN=https://knck.pl
|
||||
RATE_LIMIT_WINDOW_MS=900000 # 15 minutes
|
||||
RATE_LIMIT_MAX_REQUESTS=5 # 5 requests per window
|
||||
18
backend/Dockerfile
Normal file
18
backend/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3001
|
||||
|
||||
# Start the application
|
||||
CMD ["npm", "start"]
|
||||
22
backend/package.json
Normal file
22
backend/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "knck-contact-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Contact form backend for knck.pl",
|
||||
"main": "src/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.18.3",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"helmet": "^7.1.0",
|
||||
"nodemailer": "^6.9.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.0"
|
||||
}
|
||||
}
|
||||
89
backend/src/index.js
Normal file
89
backend/src/index.js
Normal file
@ -0,0 +1,89 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import dotenv from 'dotenv';
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.use(helmet());
|
||||
app.use(express.json());
|
||||
app.use(cors({
|
||||
origin: process.env.CORS_ORIGIN,
|
||||
methods: ['POST']
|
||||
}));
|
||||
|
||||
// Rate limiting
|
||||
const limiter = rateLimit({
|
||||
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 900000, // 15 minutes
|
||||
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 5 // 5 requests per window
|
||||
});
|
||||
app.use('/api/contact', limiter);
|
||||
|
||||
// Email transporter setup
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT),
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS
|
||||
}
|
||||
});
|
||||
|
||||
// Contact form endpoint
|
||||
app.post('/api/contact', async (req, res) => {
|
||||
try {
|
||||
const { name, email, message } = req.body;
|
||||
|
||||
// Validate input
|
||||
if (!name || !email || !message) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
// Email content
|
||||
const mailOptions = {
|
||||
from: process.env.SMTP_FROM,
|
||||
to: process.env.SMTP_TO,
|
||||
subject: `New Contact Form Submission from ${name}`,
|
||||
text: `
|
||||
Name: ${name}
|
||||
Email: ${email}
|
||||
|
||||
Message:
|
||||
${message}
|
||||
`,
|
||||
html: `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${name}</p>
|
||||
<p><strong>Email:</strong> ${email}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${message.replace(/\n/g, '<br>')}</p>
|
||||
`
|
||||
};
|
||||
|
||||
// Send email
|
||||
await transporter.sendMail(mailOptions);
|
||||
|
||||
res.status(200).json({ message: 'Email sent successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error sending email:', error);
|
||||
res.status(500).json({ error: 'Failed to send email' });
|
||||
}
|
||||
});
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({ status: 'ok' });
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(port, () => {
|
||||
console.log(`Server running on port ${port}`);
|
||||
});
|
||||
28
eslint.config.js
Normal file
28
eslint.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
14
index.html
Normal file
14
index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Jakub Kaniecki - Automation Engineer & Tech Enthusiast" />
|
||||
<title>Jakub Kaniecki | knck.pl</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
102
k8s/combined-deployment.yaml
Normal file
102
k8s/combined-deployment.yaml
Normal file
@ -0,0 +1,102 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: knck-app
|
||||
labels:
|
||||
app: knck-app
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: knck-app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: knck-app
|
||||
spec:
|
||||
containers:
|
||||
- name: knck-frontend
|
||||
image: knck-frontend:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
- name: knck-backend
|
||||
image: knck-backend:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3001
|
||||
env:
|
||||
- name: PORT
|
||||
value: "3001"
|
||||
- name: NODE_ENV
|
||||
value: "production"
|
||||
- name: SMTP_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knck-secrets
|
||||
key: smtp-host
|
||||
- name: SMTP_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knck-secrets
|
||||
key: smtp-port
|
||||
- name: SMTP_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knck-secrets
|
||||
key: smtp-user
|
||||
- name: SMTP_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knck-secrets
|
||||
key: smtp-pass
|
||||
- name: SMTP_FROM
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knck-secrets
|
||||
key: smtp-from
|
||||
- name: SMTP_TO
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knck-secrets
|
||||
key: smtp-to
|
||||
- name: CORS_ORIGIN
|
||||
value: "https://knck.pl"
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "200m"
|
||||
memory: "256Mi"
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3001
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3001
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
36
k8s/combined-ingress.yaml
Normal file
36
k8s/combined-ingress.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: knck-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "8m"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- knck.pl
|
||||
- api.knck.pl
|
||||
secretName: knck-tls
|
||||
rules:
|
||||
- host: knck.pl
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: knck-app
|
||||
port:
|
||||
number: 80
|
||||
- host: api.knck.pl
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: knck-app
|
||||
port:
|
||||
number: 3001
|
||||
17
k8s/combined-service.yaml
Normal file
17
k8s/combined-service.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: knck-app
|
||||
spec:
|
||||
selector:
|
||||
app: knck-app
|
||||
ports:
|
||||
- name: frontend
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
- name: backend
|
||||
protocol: TCP
|
||||
port: 3001
|
||||
targetPort: 3001
|
||||
type: ClusterIP
|
||||
13
k8s/secrets.yaml
Normal file
13
k8s/secrets.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: knck-secrets
|
||||
type: Opaque
|
||||
data:
|
||||
# These values should be base64 encoded
|
||||
smtp-host: c210cC5tYWlsLm1lLmNvbQ== # smtp.mail.me.com
|
||||
smtp-port: NTg3 # 587
|
||||
smtp-user: eW91ci1lbWFpbEBtZS5jb20= # your-email@me.com
|
||||
smtp-pass: eW91ci1hcHAtc3BlY2lmaWMtcGFzc3dvcmQ= # your-app-specific-password
|
||||
smtp-from: eW91ci1lbWFpbEBtZS5jb20= # your-email@me.com
|
||||
smtp-to: eW91ci1lbWFpbEBtZS5jb20= # your-email@me.com
|
||||
25
nginx.conf
Normal file
25
nginx.conf
Normal file
@ -0,0 +1,25 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 10240;
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
}
|
||||
4660
package-lock.json
generated
Normal file
4660
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "knck_pl",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"gray-matter": "^4.0.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^15.15.0",
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
"vite": "^6.2.0"
|
||||
}
|
||||
}
|
||||
4
public/favicon.svg
Normal file
4
public/favicon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="#1a1a1a"/>
|
||||
<path d="M9 7V25H12V17L20 25H24L15 16L24 7H20L12 15V7H9Z" fill="#3b82f6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 235 B |
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
62
src/App.css
Normal file
62
src/App.css
Normal file
@ -0,0 +1,62 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
line-height: 1.6;
|
||||
background-color: #121212;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
24
src/App.tsx
Normal file
24
src/App.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import Home from './pages/Home';
|
||||
import Blog from './pages/Blog';
|
||||
import Contact from './pages/Contact';
|
||||
import Navbar from './components/Navbar';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<div className="app">
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/blog" element={<Blog />} />
|
||||
<Route path="/blog/:slug" element={<Blog />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
1
src/assets/react.svg
Normal file
1
src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
246
src/components/LandingPage.module.css
Normal file
246
src/components/LandingPage.module.css
Normal file
@ -0,0 +1,246 @@
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
padding-top: 80px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hero {
|
||||
min-height: 60vh;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 4rem;
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.profileSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.profileImage {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
border: 3px solid #3b82f6;
|
||||
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.profileImage img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.socialIcons {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.iconLink {
|
||||
color: #a0a0a0;
|
||||
font-size: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.iconLink:hover {
|
||||
color: #3b82f6;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.heroContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(45deg, #3b82f6, #60a5fa);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.5rem;
|
||||
color: #a0a0a0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.socialLinks {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ctaButton {
|
||||
padding: 0.8rem 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ctaButton:hover {
|
||||
background-color: #2563eb;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.about {
|
||||
text-align: left;
|
||||
max-width: 800px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.about h2 {
|
||||
color: #ffffff;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.about p {
|
||||
color: #a0a0a0;
|
||||
line-height: 1.8;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.expertise, .interests {
|
||||
max-width: 1200px;
|
||||
margin: 4rem auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.expertiseGrid, .interestsGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.expertise h2, .interests h2 {
|
||||
color: #ffffff;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.expertiseItem, .interestItem {
|
||||
background-color: #1a1a1a;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.3s ease;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
font-size: 2.5rem;
|
||||
color: #3b82f6;
|
||||
margin-bottom: 1.5rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.expertiseItem:hover .iconWrapper,
|
||||
.interestItem:hover .iconWrapper {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.expertiseItem:hover, .interestItem:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.expertiseItem h3, .interestItem h3 {
|
||||
color: #3b82f6;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.expertiseItem p, .interestItem p {
|
||||
color: #a0a0a0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background-color: #1a1a1a;
|
||||
color: #a0a0a0;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profileSection {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.profileImage {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.about {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.socialLinks {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ctaButton {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.socialIcons {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.iconLink {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.expertise, .interests {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.expertiseGrid, .interestsGrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
117
src/components/LandingPage.tsx
Normal file
117
src/components/LandingPage.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styles from './LandingPage.module.css';
|
||||
import {
|
||||
FaGithub,
|
||||
FaInstagram,
|
||||
FaLinkedin,
|
||||
FaCode,
|
||||
FaRobot,
|
||||
FaPython,
|
||||
FaGamepad,
|
||||
FaCamera,
|
||||
FaHeart
|
||||
} from 'react-icons/fa';
|
||||
|
||||
const LandingPage: FC = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.main}>
|
||||
<section className={styles.hero}>
|
||||
<div className={styles.profileSection}>
|
||||
<div className={styles.profileImage}>
|
||||
<img src="https://picsum.photos/400/400" alt="Jakub Kaniecki" />
|
||||
</div>
|
||||
<div className={styles.socialIcons}>
|
||||
<a href="https://github.com/yourusername" target="_blank" rel="noopener noreferrer" className={styles.iconLink}>
|
||||
<FaGithub />
|
||||
</a>
|
||||
<a href="https://linkedin.com/in/yourusername" target="_blank" rel="noopener noreferrer" className={styles.iconLink}>
|
||||
<FaLinkedin />
|
||||
</a>
|
||||
<a href="https://instagram.com/yourusername" target="_blank" rel="noopener noreferrer" className={styles.iconLink}>
|
||||
<FaInstagram />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.heroContent}>
|
||||
<h1 className={styles.title}>Jakub Kaniecki</h1>
|
||||
<p className={styles.subtitle}>Automation Engineer & Tech Enthusiast</p>
|
||||
<div className={styles.socialLinks}>
|
||||
<Link to="/blog" className={styles.ctaButton}>Read My Blog</Link>
|
||||
<Link to="/contact" className={styles.ctaButton}>Get in Touch</Link>
|
||||
</div>
|
||||
<div className={styles.about}>
|
||||
<h2>About Me</h2>
|
||||
<p>
|
||||
I'm passionate about making technology work smarter. With expertise in automation,
|
||||
I help businesses streamline their processes and improve efficiency through
|
||||
modern solutions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.expertise}>
|
||||
<h2>What I Do</h2>
|
||||
<div className={styles.expertiseGrid}>
|
||||
<div className={styles.expertiseItem}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<FaCode />
|
||||
</div>
|
||||
<h3>DevOps</h3>
|
||||
<p>Building and maintaining robust CI/CD pipelines, infrastructure automation, and cloud solutions.</p>
|
||||
</div>
|
||||
<div className={styles.expertiseItem}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<FaRobot />
|
||||
</div>
|
||||
<h3>RPA</h3>
|
||||
<p>Automating repetitive tasks and business processes to increase productivity and reduce errors.</p>
|
||||
</div>
|
||||
<div className={styles.expertiseItem}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<FaPython />
|
||||
</div>
|
||||
<h3>Python Development</h3>
|
||||
<p>Creating efficient and scalable solutions for automation and data processing needs.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.interests}>
|
||||
<h2>Beyond Tech</h2>
|
||||
<div className={styles.interestsGrid}>
|
||||
<div className={styles.interestItem}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<FaGamepad />
|
||||
</div>
|
||||
<h3>Counter-Strike</h3>
|
||||
<p>Competitive gaming enthusiast, always up for strategic team play.</p>
|
||||
</div>
|
||||
<div className={styles.interestItem}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<FaCamera />
|
||||
</div>
|
||||
<h3>Photography</h3>
|
||||
<p>Capturing moments and perspectives through the lens.</p>
|
||||
</div>
|
||||
<div className={styles.interestItem}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<FaHeart />
|
||||
</div>
|
||||
<h3>Open Source</h3>
|
||||
<p>Contributing to and learning from the developer community.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer className={styles.footer}>
|
||||
<p>© 2024 knck.pl. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LandingPage;
|
||||
49
src/components/Navbar.module.css
Normal file
49
src/components/Navbar.module.css
Normal file
@ -0,0 +1,49 @@
|
||||
.header {
|
||||
padding: 1rem 2rem;
|
||||
background-color: rgba(18, 18, 18, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navLinks {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.navLinks a {
|
||||
text-decoration: none;
|
||||
color: #a0a0a0;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.navLinks a:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navLinks {
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
20
src/components/Navbar.tsx
Normal file
20
src/components/Navbar.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styles from './Navbar.module.css';
|
||||
|
||||
const Navbar: FC = () => {
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<nav className={styles.nav}>
|
||||
<Link to="/" className={styles.logo}>knck.pl</Link>
|
||||
<div className={styles.navLinks}>
|
||||
<Link to="/">Home</Link>
|
||||
<Link to="/blog">Blog</Link>
|
||||
<Link to="/contact">Contact</Link>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
34
src/components/blog/BlogCard.module.css
Normal file
34
src/components/blog/BlogCard.module.css
Normal file
@ -0,0 +1,34 @@
|
||||
.card {
|
||||
display: block;
|
||||
padding: 1.5rem;
|
||||
background-color: #1a1a1a;
|
||||
border-radius: 10px;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #ffffff;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.date {
|
||||
color: #3b82f6;
|
||||
font-size: 0.9rem;
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.excerpt {
|
||||
color: #a0a0a0;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
28
src/components/blog/BlogCard.tsx
Normal file
28
src/components/blog/BlogCard.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styles from './BlogCard.module.css';
|
||||
|
||||
interface BlogPost {
|
||||
title: string;
|
||||
date: string;
|
||||
excerpt: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface BlogCardProps {
|
||||
post: BlogPost;
|
||||
}
|
||||
|
||||
const BlogCard: FC<BlogCardProps> = ({ post }) => {
|
||||
return (
|
||||
<Link to={`/blog/${post.slug}`} className={styles.card}>
|
||||
<article>
|
||||
<h2 className={styles.title}>{post.title}</h2>
|
||||
<time className={styles.date}>{new Date(post.date).toLocaleDateString()}</time>
|
||||
<p className={styles.excerpt}>{post.excerpt}</p>
|
||||
</article>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogCard;
|
||||
40
src/content/blog/getting-started-with-react.md
Normal file
40
src/content/blog/getting-started-with-react.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Getting Started with React
|
||||
date: '2024-03-20'
|
||||
author: Jakub Kaniecki
|
||||
excerpt: Learn the basics of React and how to create your first component
|
||||
---
|
||||
|
||||
# Getting Started with React
|
||||
|
||||
React is a powerful JavaScript library for building user interfaces. In this post, we'll explore the fundamentals of React and how to get started with your first project.
|
||||
|
||||
## What is React?
|
||||
|
||||
React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called "components".
|
||||
|
||||
## Key Concepts
|
||||
|
||||
1. **Components**: The building blocks of React applications
|
||||
2. **Props**: How data flows between components
|
||||
3. **State**: Managing dynamic data in components
|
||||
4. **Hooks**: Modern way to use state and other React features
|
||||
|
||||
## Creating Your First Component
|
||||
|
||||
Here's a simple example of a React component:
|
||||
|
||||
```jsx
|
||||
function Welcome(props) {
|
||||
return <h1>Hello, {props.name}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Set up your development environment
|
||||
2. Create your first React project
|
||||
3. Learn about component lifecycle
|
||||
4. Explore React hooks
|
||||
|
||||
Stay tuned for more React tutorials!
|
||||
51
src/content/blog/typescript-best-practices.md
Normal file
51
src/content/blog/typescript-best-practices.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: TypeScript Best Practices
|
||||
date: '2024-03-21'
|
||||
author: Jakub Kaniecki
|
||||
excerpt: Essential TypeScript practices for writing better code
|
||||
---
|
||||
|
||||
# TypeScript Best Practices
|
||||
|
||||
TypeScript has become the de facto standard for building large-scale JavaScript applications. Let's explore some best practices that will help you write better TypeScript code.
|
||||
|
||||
## Type Safety
|
||||
|
||||
TypeScript's main benefit is its type system. Here are some key practices:
|
||||
|
||||
1. Always define return types for functions
|
||||
2. Use interfaces for object shapes
|
||||
3. Leverage type inference when possible
|
||||
4. Avoid using `any` type
|
||||
|
||||
## Example
|
||||
|
||||
Here's an example of good TypeScript practices:
|
||||
|
||||
```typescript
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
function getUserById(id: number): Promise<User> {
|
||||
return fetch(`/api/users/${id}`).then(res => res.json());
|
||||
}
|
||||
```
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. Overusing type assertions
|
||||
2. Ignoring compiler errors
|
||||
3. Not using strict mode
|
||||
4. Mixing `any` with strict types
|
||||
|
||||
## Tips for Better TypeScript Code
|
||||
|
||||
1. Enable strict mode
|
||||
2. Use type guards
|
||||
3. Leverage utility types
|
||||
4. Write proper type definitions
|
||||
|
||||
Remember: TypeScript is a tool to help you write better code, not a hindrance!
|
||||
68
src/index.css
Normal file
68
src/index.css
Normal file
@ -0,0 +1,68 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
159
src/pages/Blog.module.css
Normal file
159
src/pages/Blog.module.css
Normal file
@ -0,0 +1,159 @@
|
||||
.blogList {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 6rem 2rem 2rem;
|
||||
}
|
||||
|
||||
.blogList h1 {
|
||||
color: #ffffff;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.yearGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.year {
|
||||
color: #3b82f6;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.monthGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.month {
|
||||
color: #ffffff;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.posts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.blogPost {
|
||||
max-width: 800px;
|
||||
margin: 6rem auto 2rem;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.blogPost article {
|
||||
background-color: #1a1a1a;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.blogPost h1 {
|
||||
color: #ffffff;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.blogPost time {
|
||||
color: #3b82f6;
|
||||
font-size: 1rem;
|
||||
display: block;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
color: #ffffff;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
color: #ffffff;
|
||||
font-size: 1.8rem;
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.content code {
|
||||
background-color: #2d2d2d;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.content pre {
|
||||
background-color: #2d2d2d;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.content pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content ul, .content ol {
|
||||
margin: 1.5rem 0;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.content li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.blogList {
|
||||
padding: 5rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.blogPost {
|
||||
padding: 0 1rem;
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.blogPost h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.year {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.month {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.posts {
|
||||
grid-template-columns: 1fr;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: calc(100vh - 80px);
|
||||
color: #ffffff;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
152
src/pages/Blog.tsx
Normal file
152
src/pages/Blog.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import BlogCard from '../components/blog/BlogCard';
|
||||
import styles from './Blog.module.css';
|
||||
import matter from 'gray-matter';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
// Make Buffer available globally
|
||||
(globalThis as any).Buffer = Buffer;
|
||||
|
||||
interface BlogPost {
|
||||
title: string;
|
||||
date: string;
|
||||
excerpt: string;
|
||||
content: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface GroupedPosts {
|
||||
[year: string]: {
|
||||
[month: string]: BlogPost[];
|
||||
};
|
||||
}
|
||||
|
||||
const Blog: FC = () => {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const [posts, setPosts] = useState<BlogPost[]>([]);
|
||||
const [groupedPosts, setGroupedPosts] = useState<GroupedPosts>({});
|
||||
const [currentPost, setCurrentPost] = useState<BlogPost | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const loadPosts = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log('Starting to load posts...');
|
||||
|
||||
// Import all markdown files
|
||||
const postModules = import.meta.glob('../content/blog/*.md', {
|
||||
query: '?raw',
|
||||
import: 'default'
|
||||
});
|
||||
|
||||
console.log('Found post modules:', Object.keys(postModules));
|
||||
|
||||
const loadedPosts = await Promise.all(
|
||||
Object.entries(postModules).map(async ([path, getContent]) => {
|
||||
console.log('Loading content from:', path);
|
||||
const content = await getContent();
|
||||
const { data, content: markdownContent } = matter(content);
|
||||
const slug = path.split('/').pop()?.replace('.md', '') || '';
|
||||
|
||||
console.log('Loaded post:', { path, slug, data });
|
||||
|
||||
return {
|
||||
...data,
|
||||
content: markdownContent,
|
||||
slug,
|
||||
} as BlogPost;
|
||||
})
|
||||
);
|
||||
|
||||
console.log('All posts loaded:', loadedPosts);
|
||||
|
||||
const sortedPosts = loadedPosts.sort(
|
||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||
);
|
||||
|
||||
setPosts(sortedPosts);
|
||||
|
||||
// Group posts by year and month
|
||||
const grouped: GroupedPosts = {};
|
||||
sortedPosts.forEach(post => {
|
||||
const date = new Date(post.date);
|
||||
const year = date.getFullYear().toString();
|
||||
const month = date.toLocaleString('default', { month: 'long' });
|
||||
|
||||
if (!grouped[year]) {
|
||||
grouped[year] = {};
|
||||
}
|
||||
if (!grouped[year][month]) {
|
||||
grouped[year][month] = [];
|
||||
}
|
||||
|
||||
grouped[year][month].push(post);
|
||||
});
|
||||
|
||||
console.log('Grouped posts:', grouped);
|
||||
setGroupedPosts(grouped);
|
||||
|
||||
if (slug) {
|
||||
const post = sortedPosts.find(p => p.slug === slug);
|
||||
if (post) setCurrentPost(post);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading blog posts:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadPosts();
|
||||
}, [slug]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className={styles.loading}>
|
||||
<h1>Loading...</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (slug && currentPost) {
|
||||
return (
|
||||
<div className={styles.blogPost}>
|
||||
<article>
|
||||
<h1>{currentPost.title}</h1>
|
||||
<time>{new Date(currentPost.date).toLocaleDateString()}</time>
|
||||
<div className={styles.content}>
|
||||
<ReactMarkdown>{currentPost.content}</ReactMarkdown>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.blogList}>
|
||||
<h1>Blog Posts</h1>
|
||||
<div className={styles.timeline}>
|
||||
{Object.entries(groupedPosts).map(([year, months]) => (
|
||||
<div key={year} className={styles.yearGroup}>
|
||||
<h2 className={styles.year}>{year}</h2>
|
||||
{Object.entries(months).map(([month, posts]) => (
|
||||
<div key={`${year}-${month}`} className={styles.monthGroup}>
|
||||
<h3 className={styles.month}>{month}</h3>
|
||||
<div className={styles.posts}>
|
||||
{posts.map((post) => (
|
||||
<BlogCard key={post.slug} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Blog;
|
||||
160
src/pages/Contact.module.css
Normal file
160
src/pages/Contact.module.css
Normal file
@ -0,0 +1,160 @@
|
||||
.contact {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 6rem 2rem 2rem;
|
||||
}
|
||||
|
||||
.contact h1 {
|
||||
color: #ffffff;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 4rem;
|
||||
background-color: #1a1a1a;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.info h2 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.info p {
|
||||
color: #a0a0a0;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.details {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.details p {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.social {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.social a {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.social a:hover {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.formGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.formGroup label {
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.formGroup input,
|
||||
.formGroup textarea {
|
||||
padding: 0.8rem;
|
||||
background-color: #2d2d2d;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 5px;
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.formGroup textarea {
|
||||
min-height: 150px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.formGroup input:focus,
|
||||
.formGroup textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.formGroup input:disabled,
|
||||
.formGroup textarea:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
padding: 1rem 2rem;
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.submitButton:hover:not(:disabled) {
|
||||
background-color: #2563eb;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.submitButton:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: rgba(34, 197, 94, 0.1);
|
||||
border: 1px solid rgba(34, 197, 94, 0.2);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.contact {
|
||||
padding: 5rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.contact h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
130
src/pages/Contact.tsx
Normal file
130
src/pages/Contact.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
import { FC, useState } from 'react';
|
||||
import styles from './Contact.module.css';
|
||||
|
||||
const Contact: FC = () => {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
message: '',
|
||||
});
|
||||
const [status, setStatus] = useState<{
|
||||
type: 'success' | 'error' | null;
|
||||
message: string;
|
||||
}>({ type: null, message: '' });
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
setStatus({ type: null, message: '' });
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.knck.pl/api/contact', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to send message');
|
||||
}
|
||||
|
||||
setStatus({
|
||||
type: 'success',
|
||||
message: 'Message sent successfully! I will get back to you soon.',
|
||||
});
|
||||
setFormData({ name: '', email: '', message: '' });
|
||||
} catch (error) {
|
||||
setStatus({
|
||||
type: 'error',
|
||||
message: error instanceof Error ? error.message : 'Failed to send message',
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.contact}>
|
||||
<h1>Contact Me</h1>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.info}>
|
||||
<h2>Get in Touch</h2>
|
||||
<p>Feel free to reach out to me for any questions or opportunities.</p>
|
||||
<div className={styles.details}>
|
||||
<p>Email: jakub@knck.pl</p>
|
||||
<p>Location: Poland</p>
|
||||
<div className={styles.social}>
|
||||
<a href="https://github.com/yourusername" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
<a href="https://linkedin.com/in/yourusername" target="_blank" rel="noopener noreferrer">LinkedIn</a>
|
||||
<a href="https://twitter.com/yourusername" target="_blank" rel="noopener noreferrer">Twitter</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className={styles.form}>
|
||||
{status.type && (
|
||||
<div className={`${styles.status} ${styles[status.type]}`}>
|
||||
{status.message}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="name">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="message">Message</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
required
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className={styles.submitButton}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? 'Sending...' : 'Send Message'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
7
src/pages/Home.tsx
Normal file
7
src/pages/Home.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import LandingPage from '../components/LandingPage';
|
||||
|
||||
const Home = () => {
|
||||
return <LandingPage />;
|
||||
};
|
||||
|
||||
export default Home;
|
||||
9
src/vite-env.d.ts
vendored
Normal file
9
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaGlob {
|
||||
[key: string]: () => Promise<string>;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly glob: (pattern: string, options?: { as?: 'raw' }) => ImportMetaGlob;
|
||||
}
|
||||
26
tsconfig.app.json
Normal file
26
tsconfig.app.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
tsconfig.node.json
Normal file
24
tsconfig.node.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
19
vite.config.ts
Normal file
19
vite.config.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
define: {
|
||||
global: {},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
buffer: 'buffer',
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['buffer'],
|
||||
},
|
||||
assetsInclude: ['**/*.md'],
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user