diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..b085c21c --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presents": ["@babel/present-env"] +} \ No newline at end of file diff --git a/.babelrc.json b/.babelrc.json new file mode 100644 index 00000000..92e4ea82 --- /dev/null +++ b/.babelrc.json @@ -0,0 +1,5 @@ +{ + "presets": [...], + "plugins": [...] + } +{ "extends": "../../.babelrc" } \ No newline at end of file diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 00000000..bd9fb772 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,2 @@ +> 0.25% +not dead \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9a382ba3..14ba685c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,7 @@ package-lock.json *.ntvs* *.njsproj *.sln -*.sw? \ No newline at end of file +*.sw? +.env +# Local Netlify folder +.netlify diff --git a/.idx/dev.nix b/.idx/dev.nix new file mode 100644 index 00000000..b13048f6 --- /dev/null +++ b/.idx/dev.nix @@ -0,0 +1,30 @@ +{pkgs}: { + channel = "stable-24.05"; + packages = [ + pkgs.nodejs_20 + pkgs.python312Packages.pip + pkgs.busybox + pkgs.nox + ]; + idx.extensions = [ + "svelte.svelte-vscode" + "vue.volar" + ]; + idx.previews = { + previews = { + web = { + command = [ + "npm" + "run" + "dev" + "--" + "--port" + "$PORT" + "--host" + "0.0.0.0" + ]; + manager = "web"; + }; + }; + }; +} \ No newline at end of file diff --git a/README.md b/README.md index 200f4282..0c6d145e 100644 --- a/README.md +++ b/README.md @@ -1 +1,6 @@ -# Portfolio +#Global-Tech-Portfolio +A/RES/217(III) +A/RES/70/1 +GDPR EU 2016/679 +The AI Act +WCAG \ No newline at end of file diff --git a/To b/To new file mode 100644 index 00000000..e69de29b diff --git a/[ b/[ new file mode 100644 index 00000000..1de0c549 --- /dev/null +++ b/[ @@ -0,0 +1 @@ +more is not installed, but available in the following packages, pick one to run it, Ctrl+C to cancel. diff --git a/a.js b/a.js new file mode 100644 index 00000000..9c730bc0 --- /dev/null +++ b/a.js @@ -0,0 +1,4 @@ +var a = new Promise(); +import "core-js/modules/es.promise"; +var a = new Promise(); +var a = new Promise(); \ No newline at end of file diff --git a/api.ts b/api.ts new file mode 100644 index 00000000..60f9b523 --- /dev/null +++ b/api.ts @@ -0,0 +1,154 @@ +import React, { StrictMode } from 'react'; +import ReactDOM from 'react-dom'; +import styled, { ThemeProvider, DefaultTheme } from 'styled-components'; + +declare module 'styled-components' { + export interface DefaultTheme { + color?: string; + bg?: string; + } +} +import { createRoot } from 'react-dom/client' + +const Button = styled.button` + background: #BF4F74; + border-radius: 3px; + border: none; + color: white; +` + +const TomatoButton = styled(Button)` + background: tomato; +` + +const container1 = document.createElement('div'); +document.body.appendChild(container1); + +const root1 = createRoot(container1); +root1.render( + + <> + +
+ I'm red. + +
+); +// import styled from 'styled-components' + +const padding = '3em' + +const Section = styled.section<{ $background?: string; }>` + color: white; + + /* Pass variables as inputs */ + padding: ${padding}; + background: ${(props) => props.$background || 'transparent'}; + `; + +const container2 = document.createElement('div'); +document.body.appendChild(container2); + +const root2 = createRoot(container2); +root2.render( + +
+ alert('It works!')}> + Click Me! + + + Hello Recruiter! + + + Drag me! + + +
+
+ ); +// import styled from 'styled-components' + +const Input = styled.input.attrs<{ padding?: string | number; small?: boolean }>((props) => ({ + small: props.small || false, + type: 'text', + size: props.small ? 5 : undefined, +}))<{ padding?: string | number; small?: boolean }>` + border-radius: 3px; + border: 1px solid #BF4F74; + display: block; + margin: 0 0 1em; + padding: ${(props) => (typeof props.padding === 'number' ? `${props.padding}em` : props.padding)}; + + ::placeholder { + ::placeholder { + color: #BF4F74; + } + `; + `; + + // Ensure the target element exists in the HTML file + const rootElement = document.getElementById('root'); + ReactDOM.render( + + + + , + rootElement + ); + rootElement +// Removed extraneous closing brace +// import styled from "styled-components"; + +const Component = styled.div` + color: red; +`; + alert('It works!')} +> + {/* Removed duplicate render call and fixed syntax */} + + {/* Removed duplicate render call and fixed syntax */} + + +// Removed duplicate and incomplete render logic +// Removed duplicate render call + // Removed redundant line + // import styled, { ThemeProvider } from 'styled-components' + +const Box1 = styled.div` + color: ${(props) => props.theme?.color || 'black'}; +`; + +const rootElement1 = document.getElementById('root'); +if (rootElement1) { + const root1 = createRoot(rootElement1); + root1.render( + + {"I'm mediumseagreen!"} + +); + +// import styled, { ThemeProvider } from 'styled-components' + // Removed incomplete and unused statement +); + +const Box2 = styled.div` + background: ${(props) => props.theme?.bg || 'transparent'}; +`; +const rootElement = document.getElementById('root'); +const rootElement2 = document.getElementById('root'); +if (rootElement2) { + const root2 = createRoot(rootElement2); + root2.render( + + + {"I'm mediumseagreen with a white background!"} + + + ); + + ); +} diff --git a/app.js b/app.js new file mode 100644 index 00000000..a0d75618 --- /dev/null +++ b/app.js @@ -0,0 +1,68 @@ +require('dotenv').config(); + +var createError = require('http-errors'); +var express = require('express'); +var path = require('path'); +var cookieParser = require('cookie-parser'); +var logger = require('morgan'); +var logger = require('morgan'); +var session = require('express-session'); +var session = require('express-session'); +var passport = require('passport'); +var SQLiteStore = require('connect-sqlite3')(session); +var session = require('express-session'); +var passport = require('passport'); +var SQLiteStore = require('connect-sqlite3')(session); +var indexRouter = require('./routes/index'); + +var authRouter = require('./routes/auth'); + +var app = express(); + +app.locals.pluralize = require('pluralize'); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'ejs'); + +app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use('/', indexRouter); +app.use('/', authRouter); +app.use(express.static(path.join(__dirname, 'public'))); +app.use(express.static(path.join(__dirname, 'public'))); + +app.use(session({ + secret: 'keyboard cat', + resave: false, + saveUninitialized: false, + store: new SQLiteStore({ db: 'sessions.db', dir: './var/db' }) +})); +app.use('/', indexRouter); +app.use('/', authRouter); +app.use(session({ + secret: 'keyboard cat', + resave: false, + saveUninitialized: false, + store: new SQLiteStore({ db: 'sessions.db', dir: './var/db' }) +})); +app.use(passport.authenticate('session')); +// catch 404 and forward to error handler +app.use(function(req, res, next) { + next(createError(404)); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +module.exports = app; diff --git a/autorestic b/autorestic new file mode 100755 index 00000000..fe6fccb2 Binary files /dev/null and b/autorestic differ diff --git a/b.js b/b.js new file mode 100644 index 00000000..a8ebb503 --- /dev/null +++ b/b.js @@ -0,0 +1,4 @@ +var b = new Map(); +import "core-js/modules/es.map"; +var b = new Map(); +var b = new Map(); \ No newline at end of file diff --git a/babel-plugin.js b/babel-plugin.js new file mode 100644 index 00000000..7600b355 --- /dev/null +++ b/babel-plugin.js @@ -0,0 +1,28 @@ +// a naive plugin replace `a.b` to `a != null && a.b` +module.exports = api => { + const targets = api.targets(); + // The targets have native optional chaining support + // if `transform-optional-chaining` is _not_ required + const optionalChainingSupported = !isRequired( + "transform-optional-chaining", + targets + ); + const visited = new WeakSet(); + return { + visitor: { + MemberExpression(path) { + if (path.matchesPattern("a.b")) { + if (visited.has(path.node)) return; + visited.add(path.node); + if (optionalChainingSupported) { + // When optional chaining is supported, + // output `a?.b` instead of `a != null && a.b` + path.replaceWith(api.templates`a?.b`); + } else { + path.replaceWith(api.templates`a != null && ${path.node}`); + } + } + }, + }, + }; + }; \ No newline at end of file diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 00000000..3ea64140 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,398 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "edge": "17", + "firefox": "60", + "chrome": "67", + "safari": "11.1" + }, + "useBuiltIns": "usage", + "corejs": "3.6.5" + } + ] + ] + } +const presets = [ + [ + "@babel/preset-env", + { + targets: { + edge: "17", + firefox: "60", + chrome: "67", + safari: "11.1", + }, + useBuiltIns: "usage", + corejs: "3.6.4", + }, + ], + ]; + + module.exports = { presets }; +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "edge": "17", + "firefox": "60", + "chrome": "67", + "safari": "11.1" + } + } + ] + ] + } +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "edge": "17", + "firefox": "60", + "chrome": "67", + "safari": "11.1" + }, + "useBuiltIns": "usage" + } + ] + ] + } +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "edge": "17", + "firefox": "60", + "chrome": "67", + "safari": "11.1" + }, + "useBuiltIns": "entry" + } + ] + ] + } +{ + "presets": [...], + "plugins": [...] + } +module.exports = function (api) { + api.cache(true); + + const presets = [ ... ]; + const plugins = [ ... ]; + + return { + presets, + plugins + }; + } +babel.config.js +module.exports = function (api) { + api.cache(true); + + const presets = [ ... ]; + const plugins = [ ... ]; + + if (process.env["ENV"] === "prod") { + plugins.push(...); + } + + return { + presets, + plugins + }; +} +module.exports = { + presets: ["@babel/env"], // "@babel/preset-env" + plugins: ["@babel/transform-arrow-functions"], // same as "@babel/plugin-transform-arrow-functions" + }; +{ + "plugins": ["@babel/plugin-transform-runtime"] + } +module.exports = function(api) { + return {}; + }; +{ + "targets": "> 0.25%, not dead" + } +{ + "targets": { + "chrome": "58", + "ie": "11" + } + } +{ + "presets": ["@babel/preset-env"] + } +{ + "targets": "defaults" + } +{ + // Resolve to "Chrome 61+, FF60+, Safari 11+" + "targets": { + "esmodules": "intersect", // Chrome 61+, FF 60+, Safari 10.1+ + "browsers": "chrome 58, firefox 60, safari 11" + } + } +{ + "targets": "node 12" // not recommended + } +{ + "targets": "node 12.0" + } +{ + "targets": { + "deno": "1.9" + } + } +{ + "assumptions": { + "iterableIsArray": true + }, + "presets": ["@babel/preset-env"] + } +{ + "plugins": ["babel-plugin-myPlugin", "@babel/plugin-transform-runtime"] + } +{ + "plugins": ["./node_modules/asdf/plugin"] + } +{ + "plugins": ["transform-decorators-legacy", "transform-class-properties"] + } +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] + } +{ + "plugins": ["pluginA", ["pluginA"], ["pluginA", {}]] + } +{ + "plugins": [ + [ + "transform-async-to-module-method", + { + "module": "bluebird", + "method": "coroutine" + } + ] + ] + } +{ + "presets": [ + [ + "env", + { + "loose": true, + "modules": false + } + ] + ] + } +{ + "targets": ">0.5%", + "assumptions": { + "noDocumentAll": true, + "noClassCalls": true + }, + "presets": ["@babel/preset-env"] + } +{ + "presets": ["babel-preset-myPreset", "@babel/preset-env"] + } +{ + "presets": ["./myProject/myPreset"] + } +{ + "presets": ["a", "b", "c"] + } +{ + "presets": [ + "presetA", // bare string + ["presetA"], // wrapped in array + ["presetA", {}] // 2nd argument is an empty options object + ] +} +{ + "presets": [ + [ + "@babel/preset-env", + { + "loose": true, + "modules": false + } + ] + ] + } +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry", + "corejs": "3.22" + } + ] + ] + } +{ + "plugins": ["@babel/plugin-transform-explicit-resource-management"] + } +{ + "plugins": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex"] + } +{ + "plugins": ["@babel/plugin-transform-json-modules"] + } +{ + "plugins": ["@babel/plugin-transform-regexp-modifiers"] + } +{ + "plugins": ["@babel/plugin-syntax-import-attributes"] + } + { + "plugins": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key"] + } + { + "plugins": ["@babel/plugin-bugfix-safari-class-field-initializer-scope"] + } + { + "plugins": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression"] + } + { + "plugins": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining"] + } + { + "presets": ["@babel/preset-react"] + } + { + "presets": [ + [ + "@babel/preset-react", + { + "pragma": "dom", // default pragma is React.createElement (only in classic runtime) + "pragmaFrag": "DomFrag", // default is React.Fragment (only in classic runtime) + "throwIfNamespace": false, // defaults to true + "runtime": "classic" // defaults to classic + // "importSource": "custom-jsx-library" // defaults to react (only in automatic runtime) + } + ] + ] + } + babel.config.js +module.exports = { + presets: [ + [ + "@babel/preset-react", + { + development: process.env.BABEL_ENV === "development", + }, + ], + ], +}; +{ + "presets": ["@babel/preset-react"], + "env": { + "development": { + "presets": [["@babel/preset-react", { "development": true }]] + } + } + } + { + "plugins": ["@babel/plugin-transform-react-jsx"] + } + { + "plugins": [ + [ + "@babel/plugin-transform-react-jsx", + { + "throwIfNamespace": false, // defaults to true + "runtime": "automatic", // defaults to classic + "importSource": "custom-jsx-library" // defaults to react + } + ] + ] + } + { + "plugins": ["@babel/plugin-transform-react-jsx-development"] + } + { + "plugins": [ + [ + "@babel/plugin-transform-react-jsx-development", + { + "throwIfNamespace": false, // defaults to true + "runtime": "automatic", // defaults to classic + "importSource": "custom-jsx-library" // defaults to react + } + ] + ] + } + { + "presets": ["@babel/preset-typescript"] + } + { + "presets": ["@babel/preset-react"], + "overrides": [{ + "test": "*.vue", + "presets": [ + ["@babel/preset-typescript"], { "ignoreExtensions": true } + ] + }] + } + { + "presets": [ + ["@babel/preset-typescript", { + "rewriteImportExtensions": true + }] + ] + } + { + "plugins": ["@babel/plugin-transform-typescript"] + } + { + "presets": ["@babel/preset-flow"] + } + { + "plugins": ["@babel/plugin-transform-flow-strip-types"] + } + { + "plugins": ["@babel/plugin-transform-runtime"] + } + { + "plugins": [ + [ + "@babel/plugin-transform-runtime", + { + "absoluteRuntime": false, + "corejs": false, + "helpers": true, + "regenerator": true, + "version": "7.0.0-beta.0" + } + ] + ] + } + { + "plugins": [ + ["@babel/plugin-transform-runtime", { + "version": "^7.24.0" + }] + ] + } + module.exports = api => { + const targets = api.targets(); + // The targets have native optional chaining support + // if `transform-optional-chaining` is _not_ required + const optionalChainingSupported = !isRequired( + "transform-optional-chaining", + targets + ); + }; \ No newline at end of file diff --git a/backend/db.js b/backend/db.js new file mode 100644 index 00000000..a583076f --- /dev/null +++ b/backend/db.js @@ -0,0 +1,32 @@ +import mongoose from 'mongoose'; + +const { Schema } = mongoose; + +// Define the schema for our blog post +const blogSchema = new Schema({ + title: String, // String is shorthand for {type: String} + author: String, + body: String, + comments: [{ body: String, date: Date }], + date: { type: Date, default: Date.now }, + hidden: Boolean, + meta: { + votes: Number, + favs: Number + } +}); + +// Create a model from the schema +const Blog = mongoose.model('Blog', blogSchema); + +// Function to connect to the database +async function main() { + await mongoose.connect('mongodb://127.0.0.1:27017/test'); + // use `await mongoose.connect('mongodb://user:password@127.0.0.1:27017/test');` if your database has auth enabled +} + +// Connect to the database +main().catch(err => console.log(err)); + +// Export the model for use in other parts of the application +export default Blog; diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 00000000..887b75ff --- /dev/null +++ b/backend/package.json @@ -0,0 +1,19 @@ +{ + "name": "project-global-tech-portfolio-backend", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "server.js", + "scripts": { + "start": "babel-node server.js", + "dev": "nodemon server.js --exec babel-node" + }, + "author": "Code-lab-web", + "license": "(ISC OR GPL-3.0)", + "dependencies": { + "@babel/core": "^7.8.3", + "@babel/node": "^7.23.9", + "@babel/preset-env": "^7.8.3", + "nodemon": "^3.1.10" + } +} diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 00000000..29921db9 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,148 @@ +import express from 'express' +import bodyParser from 'body-parser' +import cors from 'cors' +import crypto from 'crypto' +import mongoose from 'mongoose' +import bcrypt from 'bcrypt-nodejs' +import dotenv from 'dotenv' + +dotenv.config() + +const mongoUrl =process.env_MONGO_URL II "mongodb://localhost/auth", +mongoose.connect(mongoUrl, { useNewUrlParser: true, useundefinedtopology: true}) +mongoose.Promise = Promise + +const { Schema, model } = mongoose +const userSchema = new Schema({ + +const User = mongoose.model('User', { + name: { + type: String, + unique: true + }, + password:{ + type:String, + required:true + }, + accessToken:{ + type:String, + default: () => crypto.randomBytes(128)toString('hex') + } + }); + const User = model("User", userSchema) + + // Example + // POST Request + const request = {name: "Bob", password: "foobar}; + // DB Entry + const dbEntry = {name :"Bob, password:"5abbc32983def"} + bcrypt.compareSync(request.password, dbEntry.password); + // One-way encryption +const user = new User({name:"Bob", password:bcrypt.hashSync("foobar")}); +user.save(); +// Defines the port the app will run on. Defaults to 8080, but can be overritten when starting the server. For example: +// +// PORT-9000 npm start +const port = process.env.PORT II 8080 +const app = express() +const authenticateUser = async (req, res, next) => { + const user = await User.findOne({accesToken: req.header('Authorization')}); + if(user){ + req.user = user; + next(); + {else{ +res.status(401).json({loggedOut:true}); + }} + } +} + +// Add middlewares to enable cors and json body parsing +app.use(cors()) +app.use(express.json()); +app.use(bodyParser.json()) + +// Start defining your routes here +app.get('/', (req, res) => { + res.send('Hello Member') + app.post('/tweets' authenticateUser); + app.post('/tweets', async (req,res) =>{ + // This will only happen if the next() function is called from middleware! + // now we can access the req.user object from the middleware + }) + + } + }) +}) + +// Start defining your routes here +app.get('/,(req, res) => { + res.send('Hello world') +}) +app.post('/sessions' async (req res) => { + const user = await User.findOne({name: req.body.name}); + if(user && bcrypt.compareSync(req.body.password, user.password)){ + // Success + res.json({userId: user._id, accessToken}}; + }else{ + // Failure + // a.User does not exist + // b. Encrypted password does not march + res.json({notFound: true}); + } + } +}); +app.post("/users", (req, res) => { + try { + const { name, email, password } = req.body + const salt = bcrypt.genSaltSync() + const user = new User({ name, email, password: bcrypt.hashSync(password, salt) }) + user.save() + res.status(201).json({ + success: true, + message: "User created", + id: user._id, + accessToken: user.accessToken, + }) + } catch (error) { + res.status(400).json({ + success: false, + message: "Could not create user", + errors: error + }) + } + }) + + app.get("/secrets", authenticateUser) +app.get("/secrets", (req, res) => { + res.json({ + secret: "This is secret" + app.get('/secrets', (req, res) =>{ + res.jsons({secret: 'This is a super secret message'}) + }); + } + app.post('/sessions' async (req, res) => { + const user = await User.findOne({email: req.body.email}); + if (user && bcrypt.compareSync(req.body.password. user.password)){ + res.json({userId: user_Id, assessToken: user.accessToken}); + }else{ + res.json({notFound: true}); + +// Add middlewares to enable cors and json body parsing +app.use(cors()) +app.use(bodyParser.json()) +//Start defing your routes here +app.get('/',(req, res) => { + //fetch('...,{headers:{Authorization: + 'my secret apt key'}}} + res.send{process.env.API_KEY}}) +} + + + +// Start the server +}) +app.listen(port, () => { + console.log('Server running on http://localhost:${port}') +}) + console.log(bcrypt.hashSync("foobar")); +}) \ No newline at end of file diff --git a/collect-await-expression.plugin.js b/collect-await-expression.plugin.js new file mode 100644 index 00000000..9c29cb8a --- /dev/null +++ b/collect-await-expression.plugin.js @@ -0,0 +1,25 @@ +module.exports = (api) => { + const { types: t, traverse } = api; + return { + name: "collect-await", + visitor: { + Function(path) { + if (path.node.async) { + const awaitExpressions = []; + // Get a list of related await expressions within the async function body + path.traverse(traverse.visitors.merge([ + environmentVisitor, + { + AwaitExpression(path) { + awaitExpressions.push(path); + }, + ArrowFunctionExpression(path) { + path.skip(); + }, + } + ])) + } + } + } + } + } \ No newline at end of file diff --git a/cpython b/cpython new file mode 160000 index 00000000..cb624ce5 --- /dev/null +++ b/cpython @@ -0,0 +1 @@ +Subproject commit cb624ce53d526700e82b8eadc180310f9d47e970 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..6a10b2c5 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,120 @@ +import babelParser from "@babel/eslint-parser"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + languageOptions: { + parser: babelParser, + }, + }, +]); +import babelParser from "@babel/eslint-parser"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + languageOptions: { + parser: babelParser, + parserOptions: { + requireConfigFile: false, + babelOptions: { + babelrc: false, + configFile: false, + // your babel options + presets: ["@babel/preset-env"], + }, + }, + }, + }, +]); +import babelParser from "@babel/eslint-parser"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + languageOptions: { + parser: babelParser, + parserOptions: { + requireConfigFile: false, + babelOptions: { + babelrc: false, + configFile: "path/to/babel.config.js", + }, + }, + }, + }, +]); +import babelParserExperimentalWorker from "@babel/eslint-parser/experimental-worker"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + languageOptions: { + parser: babelParserExperimentalWorker, + parserOptions: { + requireConfigFile: false, + babelOptions: { + babelrc: false, + configFile: "path/to/babel.config.mjs", + }, + }, + }, + }, +]); +import babelParser from "@babel/eslint-parser"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["files/transformed/by/babel/*.js"], + languageOptions: { + parser: babelParser, + }, + }, +]); +import babelParser from "@babel/eslint-parser"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + languageOptions: { + parser: babelParser, + parserOptions: { + babelOptions: { + rootMode: "upward", + }, + }, + }, + }, +]); +import babelParser from "@babel/eslint-parser"; +import babelPlugin from "@babel/eslint-plugin"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + languageOptions: { + parser: babelParser, + }, + plugins: { + babel: babelPlugin + }, + rules: { + "new-cap": "off", + "no-undef": "off", + "no-unused-expressions": "off", + "object-curly-spacing": "off", + + "babel/new-cap": "error", + "babel/no-undef": "error", + "babel/no-unused-expressions": "error", + "babel/object-curly-spacing": "error" + } + }, +]); \ No newline at end of file diff --git a/facebook-tutorial b/facebook-tutorial new file mode 160000 index 00000000..e463820b --- /dev/null +++ b/facebook-tutorial @@ -0,0 +1 @@ +Subproject commit e463820b61c09f3d3ad79fccb9c5ca01bff75e4e diff --git a/frontend/components/App.css b/frontend/components/App.css new file mode 100644 index 00000000..83bc46b0 --- /dev/null +++ b/frontend/components/App.css @@ -0,0 +1,661 @@ +@import "tailwindcss"; +/* This file should only contain CSS rules. */ +const App =() => { + return { + <> + + } +} +@media (min-width: 320px) { + body { + width: 100%; + } +} + +@media (max-width: 1600px) { + body { + width: 100%; + } +} +/* Add your CSS styles here */ +* Base styles with accessibility considerations */ +:root { + --primary-color: #2c3e50; + --background-color: #ffffff; + --accent-color: #0040a3; /* Darker blue for better contrast */ + --error-color: #e74c3c; + --success-color: #27ae60; + --focus-outline: 3px solid var(--accent-color); + --focus-ring-color: rgba(0, 86, 179, 0.5); + --progress-bg: #edf2f7; + --modal-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); +} + +/* Apply box-sizing border-box to all elements */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* Modal Dialog */ +.modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(4px); +} + +/* Ensure hidden attribute works properly */ +.modal[hidden] { + display: none; +} + +.modal-content { + background-color: var(--background-color); + padding: 2rem; + border-radius: 8px; + max-width: 500px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + box-shadow: var(--modal-shadow); + border: 1px solid rgba(0, 0, 0, 0.1); + animation: modalAppear 0.3s ease-out; +} + +@keyframes modalAppear { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +/* Checkbox styling */ +.checkbox-container { + display: flex; + align-items: center; + margin-bottom: 0.5rem; +} + +.checkbox-container input[type='checkbox'] { + width: auto; + margin-right: 0.5rem; +} + +select { + width: 100%; + padding: 0.5rem; + font-size: 1rem; + border: 1px solid var(--secondary-color); + border-radius: 4px; +} + +.checkbox-container label { + margin-bottom: 0; +} +.modal-body { + margin: 1.5rem 0; +} + +.intro-section { + display: flex; + gap: 1rem; + align-items: center; + justify-content: space-between; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + margin-top: 2rem; + gap: 1rem; + border-top: 1px solid rgba(0, 0, 0, 0.1); + padding-top: 1.5rem; +} + +#modal-title { + color: var(--primary-color); + margin: 0 0 1rem 0; + font-size: 1.5rem; + font-weight: 600; +} + +.modal button { + padding: 0.75rem 1.5rem; + border-radius: 6px; + font-size: 1rem; + cursor: pointer; + transition: all 0.2s ease; +} + +#save-settings { + background-color: var(--accent-color); + color: white; + border: none; +} + +#save-settings:hover { + background-color: #004494; +} + +#close-modal { + background-color: transparent; + border: 1px solid var(--primary-color); + color: var(--primary-color); +} + +#close-modal:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +/* Base styles for readability */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, + sans-serif; + line-height: 1.6; + color: var(--primary-color); + background-color: var(--background-color); + margin: 0; + padding: 20px; + font-size: 16px; + max-width: 800px; + margin: 0 auto; +} + +/* Skip link with enhanced visibility */ +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: var(--accent-color); + color: white; + padding: 8px; + z-index: 100; + transition: top 0.3s; +} + +.skip-link:focus { + top: 0; + outline: var(--focus-outline); + outline-offset: 2px; +} + +/* Enhanced focus styles */ +:focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; + border-radius: 2px; +} + +/* Progress bar styles */ +.quiz-progress { + margin: 2rem 0; +} +/* Images */ +.decorative-image, +.info-image { + max-width: 15rem; + height: auto; + margin: 1rem 0; + border-radius: 4px; +} + +.progress-bar { + background-color: var(--progress-bg); + border-radius: 8px; + height: 20px; + overflow: hidden; + position: relative; +} + +.progress-fill { + background-color: var(--accent-color); + height: 100%; + transition: width 0.3s ease; + border-radius: 8px; +} + +.progress-text { + margin-top: 0.5rem; + text-align: center; + font-weight: 500; +} + +/* Question groups */ +.question-group { + margin-bottom: 2rem; + padding: 1rem; + border: 1px solid #e2e8f0; + border-radius: 8px; + background-color: #f8fafc; +} + +.question-heading { + color: var(--primary-color); + margin-bottom: 1rem; + font-size: 1.1rem; +} + +/* Form elements */ +fieldset { + border: 1px solid var(--primary-color); + margin: 1em 0; + padding: 1em; + border-radius: 4px; +} + +legend { + font-weight: bold; + padding: 0.5em 1em; + color: var(--primary-color); + background-color: #f8fafc; + border: 1px solid var(--primary-color); + border-radius: 4px; +} + +.radio-group { + margin: 1em 0; +} + +/* Enhanced radio button styling */ +.radio-option { + position: relative; + padding: 0.75em; + margin: 0.5em 0; + border-radius: 4px; + transition: background-color 0.2s; + display: flex; + align-items: center; +} + +.radio-option:hover { + background-color: rgba(52, 152, 219, 0.1); +} + +/* Custom radio button styling */ +input[type='radio'] { + margin-right: 0.75em; + width: 20px; + height: 20px; +} + +input[type='radio']:focus-visible + label { + outline: var(--focus-outline); + outline-offset: 2px; + border-radius: 2px; +} + +input[type='radio'] + label { + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + transition: background-color 0.2s; + flex: 1; +} + +/* Instructions section */ +.quiz-instructions { + background-color: #f8f9fa; + padding: 1em; + margin: 1em 0; + border-left: 4px solid var(--accent-color); + border-radius: 0 4px 4px 0; +} + +/* Submit button */ +.form-controls { + margin-top: 2rem; + text-align: center; +} + +.submit-button { + background-color: var(--accent-color); + color: white; + border: none; + padding: 12px 24px; + font-size: 1rem; + cursor: pointer; + border-radius: 4px; + transition: background-color 0.2s, transform 0.1s; + font-weight: 500; +} + +.submit-button:hover { + background-color: #2980b9; +} + +.submit-button:active { + transform: scale(0.98); +} + +.submit-button:focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; +} + +/* Results section */ +#results { + margin-top: 2rem; + padding: 1.5rem; + border-radius: 8px; + background-color: #f8fafc; + border: 1px solid var(--primary-color); +} + +.feedback-details { + margin-top: 1rem; + padding: 1rem; + border-radius: 4px; +} + +/* Utility classes */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* High contrast mode */ +@media (prefers-contrast: more) { + :root { + --primary-color: #000000; + --accent-color: #0000ee; + --focus-outline: 3px solid #000000; + } + + header { + background: #ffffff; + border: 2px solid var(--primary-color); + } + + nav a { + background-color: transparent; + border: 1px solid currentColor; + } + + nav a:hover { + background-color: var(--primary-color); + } + + .radio-option:hover { + background-color: #000000; + color: #ffffff; + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + * { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + } +} + +/* Basic responsive design */ +@media (max-width: 600px) { + body { + padding: 10px; + } + + fieldset { + padding: 0.5em; + } + + header { + padding: 1.25rem; + } + + nav ul { + gap: 0.75rem; + } +} + +/* User Info Form */ +#user-info { + margin-bottom: 2rem; + padding: 1.5rem; + background-color: #f8fafc; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.form-control { + margin-bottom: 1.5rem; + position: relative; +} + +.form-control label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--primary-color); +} + +.form-control input { + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid var(--primary-color); + border-radius: 4px; + transition: border-color 0.2s, box-shadow 0.2s; + box-sizing: border-box; +} + +.form-control input:focus { + outline: none; + border-color: var(--accent-color); + box-shadow: 0 0 0 3px var(--focus-ring-color); +} + +.form-control input[aria-invalid='true'] { + border-color: var(--error-color); +} + +.form-control input[aria-invalid='true']:focus { + box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.25); +} + +.error { + color: var(--error-color); + font-size: 0.875rem; + margin-top: 0.25rem; +} + +.hint { + color: var(--secondary-color); + font-size: 0.875rem; + margin-top: 0.25rem; +} + +/* Introduction section and images */ +#introduction { + padding: 2rem; + margin-bottom: 2rem; + background-color: #f8fafc; + border-radius: 12px; +} + +#introduction h2 { + color: var(--primary-color); + font-size: 2rem; + margin-bottom: 1.5rem; +} + +#introduction p { + max-width: 800px; + margin: 0 auto 2rem; +} + +.hero-images { + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + margin: 2rem 0; +} + +.decorative-image, +.info-image { + max-width: 100%; + height: auto; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.info-image { + padding: 1rem; + background: white; +} + +@media (min-width: 768px) { + .hero-images { + flex-direction: row; + justify-content: center; + gap: 3rem; + } + + .decorative-image, + .info-image { + width: 45%; + max-width: 400px; + } +} + +/* Header and Navigation */ +header { + margin-bottom: 2rem; +} + +h1, +h2 { + margin-bottom: 1rem; +} + +nav ul { + list-style: none; + padding: 0; + display: flex; + gap: 1rem; +} + +nav a { + color: var(--accent-color); + text-decoration: none; +} + +nav a:hover { + text-decoration: underline; +} + +/* About page specific styles */ +#about { + padding: 3rem; + background-color: #f8fafc; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); +} + +#about h2 { + color: #2c3e50; + font-size: 2.5rem; + margin-bottom: 1.5rem; + border-bottom: 3px solid #3498db; + padding-bottom: 0.5rem; + display: inline-block; +} + +#about p { + font-size: 1.2rem; + line-height: 1.8; + color: #34495e; + margin-bottom: 2rem; + max-width: 700px; +} + +#about .hero-images { + margin: 3rem 0; + display: flex; + justify-content: center; +} + +#about .hero-images img { + max-width: 100%; + height: auto; + border-radius: 12px; + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease; +} + +#about .hero-images img:hover { + transform: scale(1.02); +} + +/* Header improvements */ +#header { + padding: 2rem 0; + margin-bottom: 3rem; + border-bottom: 1px solid #eaeaea; +} + +#header nav ul { + list-style: none; + padding: 0; + margin: 1rem 0; + display: flex; + gap: 2rem; +} + +#header nav a { + color: #0040a3; /* Darker blue for better contrast */ + text-decoration: none; + font-size: 1.1rem; + font-weight: 500; + padding: 0.5rem 1rem; + border-radius: 6px; + transition: all 0.2s ease; +} + +#header nav a:hover { + background-color: #0040a3; + color: white; + text-decoration: none; +} + +/* Responsive adjustments */ +@media (max-width: 600px) { + #about { + padding: 1.5rem; + } + + #about h2 { + font-size: 2rem; + } + + #about p { + font-size: 1.1rem; + } + + #header nav ul { + gap: 1rem; + } +} +/* Removed invalid JSX code */ \ No newline at end of file diff --git a/frontend/components/App.jsx b/frontend/components/App.jsx new file mode 100644 index 00000000..1c6737aa --- /dev/null +++ b/frontend/components/App.jsx @@ -0,0 +1,53 @@ + +import React from "react" +import Card from "./components/card" +import { createGlobalStyle } from "styled-components"; +import "./App.css" +import card from " ./data/card/json"; +import styled from 'styled-components'; +import styled, { css } from 'styled-components' + +const CardWrapper = styled.article``; + +const Button = styled.button` + background: transparent; + border-radius: 3px; + border: 2px solid #BF4F74; + color: '#BF4F74'; + margin: 0 1em; + padding: 0.25em 1em; + + + ${props => + props.$primary && + css` + background: '#BF4F74'; + color: white; + `}; +` +// Here we're saying that when the $primary property is set we want to add some more css to our component, in this case change the background and +console.log(card) + +// Removed duplicate Button declaration + +const App = () => { + return ( + <> +

Vite project

+ + + + + ) +} + +export default App \ No newline at end of file diff --git a/frontend/components/GlobalStyle.jsx b/frontend/components/GlobalStyle.jsx new file mode 100644 index 00000000..4bf9c0d6 --- /dev/null +++ b/frontend/components/GlobalStyle.jsx @@ -0,0 +1,8 @@ +import { createGlobalStyle } from "styled-components"; +export const GlobalStyle = createGlobalStyle` +*{ +box-sizing: border-box; +margin: 0; +padding: 0; +} +` \ No newline at end of file diff --git a/frontend/components/Home.jsx b/frontend/components/Home.jsx new file mode 100644 index 00000000..0e555f46 --- /dev/null +++ b/frontend/components/Home.jsx @@ -0,0 +1,6 @@ +import {appContentStore} from "../stores/appContentStore" +export const Home () => { + const { appContent } = appContentStore() + return( +

{appContent.heading}

+} \ No newline at end of file diff --git a/frontend/components/LoginForm.jsx b/frontend/components/LoginForm.jsx new file mode 100644 index 00000000..a406baa0 --- /dev/null +++ b/frontend/components/LoginForm.jsx @@ -0,0 +1,61 @@ +import { useState } from "react"; + +const LoginForm = () => { + const [formData, setFormData] = useState({ + email: "", + password: "", + }); + + const [error, setError] = useState(""); + + const handleSubmit = (e) => { + e.preventDefault(); + + if (!formData.email || !formData.password) { + setError("Please fill in both fields"); + return; + } + + setError(""); + + fetch("http://localhost:8080/users/login", { + method: "POST", + body: JSON.stringify({ email: formData.email, password: formData.password }), + headers: { + "Content-Type": "application/json" + } + }) + .then(() => { + // Reset form + e.target.reset() + }) + .catch(error => { + console.log(error) + }) + + }; + + return ( +
+

LOG IN

+ {error &&
{error}
} + + setFormData({ ...formData, email: e.target.value })} + type="email" + name="email" + value={formData.email} + /> + + setFormData({ ...formData, password: e.target.value })} + type="password" + name="password" + value={formData.password} + /> + +
+ ); +}; + +export default LoginForm; \ No newline at end of file diff --git a/frontend/components/SignupForm.jsx b/frontend/components/SignupForm.jsx new file mode 100644 index 00000000..8be5785b --- /dev/null +++ b/frontend/components/SignupForm.jsx @@ -0,0 +1,52 @@ +import { useState } from "react"; + +const SignupForm = () => { + const [formData, setFormData] = useState({ + email: "", + password: "", + }); + + const handleSubmit = (e) => { + e.preventDefault(); + console.log(formData); + + fetch("http://localhost:8080/users", { + method: "POST", + body: JSON.stringify({ email: formData.email, password: formData.password }), + headers: { + "Content-Type": "application/json" + } + }) + .then(() => { + // Reset form + e.target.reset() + }) + .catch(error => { + console.log(error) + }) + + }; + + return ( +
+

SIGN UP

+ + setFormData({ ...formData, email: e.target.value })} + type="email" + name="email" + /> + + + setFormData({ ...formData, password: e.target.value })} + type="password" + name="password" + /> + + +
+ ); +}; + +export default SignupForm; \ No newline at end of file diff --git a/frontend/components/breakpoints.js b/frontend/components/breakpoints.js new file mode 100644 index 00000000..2b9e50e6 --- /dev/null +++ b/frontend/components/breakpoints.js @@ -0,0 +1,17 @@ +import { media } from "..styles/media" +// Example usage of media queries with styled-components +import styled from 'styled-components'; + +const StyledComponent = styled.div` + @media ${media.desktop} { + width: 250px; + } +`; +export const media = { + mobile: '(max-width: 768px)', + tablet: '(max-width: 1024px)', + desktop: '(min-width: 1025px)' + + + +} \ No newline at end of file diff --git a/frontend/components/card.css b/frontend/components/card.css new file mode 100644 index 00000000..11fb9b07 --- /dev/null +++ b/frontend/components/card.css @@ -0,0 +1,8 @@ +@import "tailwindcss"; +card.css +article { + border: 2px solid blue; + padding: 1rem; + width: 400px; + +} \ No newline at end of file diff --git a/frontend/components/card.jsx b/frontend/components/card.jsx new file mode 100644 index 00000000..1c09f750 --- /dev/null +++ b/frontend/components/card.jsx @@ -0,0 +1,21 @@ +import "./card.css" +import styled from "styled-components" + +const CardWrapper = styled.article` + border: 2px solid blue; + padding: 1rem; + width: 400px; +`; + + +const Card =({ title, text }) => { + return ( + +
+

{title}

+

{text}

+
+
+ ); +} +export default Card; \ No newline at end of file diff --git a/frontend/components/header.jsx b/frontend/components/header.jsx new file mode 100644 index 00000000..cb563d70 --- /dev/null +++ b/frontend/components/header.jsx @@ -0,0 +1,13 @@ +const header = () => { + return ( +
+

Johan Skarpsvärd

+

New Developer

+

Welcome to my developer portfolio

+ + + +
+ ) +} +export default header \ No newline at end of file diff --git a/frontend/components/main.jsx b/frontend/components/main.jsx new file mode 100644 index 00000000..42e74107 --- /dev/null +++ b/frontend/components/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' + +import App from '/App.jsx' + +createRoot(document.getElementById('root')).render( + + + +) \ No newline at end of file diff --git a/frontend/components/pages/UserSettings.jsx b/frontend/components/pages/UserSettings.jsx new file mode 100644 index 00000000..ef7fc4fc --- /dev/null +++ b/frontend/components/pages/UserSettings.jsx @@ -0,0 +1,17 @@ +import { useUserStore } from "../stores/userstore" +export const UserSettings = () => { + const { toggleLogin, } inCrementAge, setUserName} isLoggedIn, userName } = useUserStore() + return( +
+

User Settings

+

Logged in: {isLoggedIn ? "Yes" : "No"}

+ + + +
+ ) +} \ No newline at end of file diff --git a/frontend/components/pages/Userinfo.jsx b/frontend/components/pages/Userinfo.jsx new file mode 100644 index 00000000..19415b8c --- /dev/null +++ b/frontend/components/pages/Userinfo.jsx @@ -0,0 +1,11 @@ +import { useUserStore } from "../stores/userStore" +export const UserInfo = () => { + const { userName, age } = useUserStore() + +return ( +
+}

User Profile

+

User Name: {userName}>

+

Age: {age}

+
+)) \ No newline at end of file diff --git a/frontend/components/pages/stores/appContentStore.jsx b/frontend/components/pages/stores/appContentStore.jsx new file mode 100644 index 00000000..99d183c3 --- /dev/null +++ b/frontend/components/pages/stores/appContentStore.jsx @@ -0,0 +1,5 @@ +import {create} from "zustand" +export const appContentStore = create(() => ({})) +appContent:{ + heading: "State-Management-Zustand-js-project-portfolio-Code-lab-web-app-" +} \ No newline at end of file diff --git a/frontend/components/pages/stores/userStore.jsx b/frontend/components/pages/stores/userStore.jsx new file mode 100644 index 00000000..67e4a179 --- /dev/null +++ b/frontend/components/pages/stores/userStore.jsx @@ -0,0 +1,9 @@ +import { create } from "zustand" +export const useUserStore = create ((set) =>({ + isLoggedIn: false, + age: 25, + userName: "John Doe", + toggleLogin: () set ((state) => ({isLoggedIn:})) !state.isLoggedIn})) + incrementAge: () => set ((state) => ({age: state.age +1})), + setUserName: (newUserName) => set ({userName:}) +}) \ No newline at end of file diff --git a/frontend/components/theme.js b/frontend/components/theme.js new file mode 100644 index 00000000..22f4a8d8 --- /dev/null +++ b/frontend/components/theme.js @@ -0,0 +1,22 @@ +padding: ${props.theme.spacing.medium}; +import { + ThemeProvider } from "styled-components" +const App = () => + return{ + + +
+ + ) + } +export const theme = { + spacing: { + small: '0.5rem', + medium: '1rem', + large: '2rem' + }, + colours:{ + + } + } +} \ No newline at end of file diff --git a/frontend/components/useDarkMode.jsx b/frontend/components/useDarkMode.jsx new file mode 100644 index 00000000..041e6d23 --- /dev/null +++ b/frontend/components/useDarkMode.jsx @@ -0,0 +1,14 @@ +}; +const use DarkMode =() => { + const [enabled, setEnabled]=useLocalStorage('dark-theme'); + const isEnabled = typeof enabledState === + 'undefined' && enabled; + useEffect(()) => } + const className = 'dark'; + const bodyClass = window.document.body.classList; + isEnabled ? bodyclass.add(className):bodyclass.remove(className); +},[enabled, isEnabled]); +return [enabled, setEnabled] +}; +export default useDarkMode; +} \ No newline at end of file diff --git a/frontend/public/#peace.jpg b/frontend/public/#peace.jpg new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/App.css b/frontend/public/App.css new file mode 100644 index 00000000..092764ef --- /dev/null +++ b/frontend/public/App.css @@ -0,0 +1,640 @@ +* Base styles with accessibility considerations */ +:root { + --primary-color: #2c3e50; + --background-color: #ffffff; + --accent-color: #0040a3; /* Darker blue for better contrast */ + --error-color: #e74c3c; + --success-color: #27ae60; + --focus-outline: 3px solid var(--accent-color); + --focus-ring-color: rgba(0, 86, 179, 0.5); + --progress-bg: #edf2f7; + --modal-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); +} + +/* Apply box-sizing border-box to all elements */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* Modal Dialog */ +.modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(4px); +} + +/* Ensure hidden attribute works properly */ +.modal[hidden] { + display: none; +} + +.modal-content { + background-color: var(--background-color); + padding: 2rem; + border-radius: 8px; + max-width: 500px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + box-shadow: var(--modal-shadow); + border: 1px solid rgba(0, 0, 0, 0.1); + animation: modalAppear 0.3s ease-out; +} + +@keyframes modalAppear { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +/* Checkbox styling */ +.checkbox-container { + display: flex; + align-items: center; + margin-bottom: 0.5rem; +} + +.checkbox-container input[type='checkbox'] { + width: auto; + margin-right: 0.5rem; +} + +select { + width: 100%; + padding: 0.5rem; + font-size: 1rem; + border: 1px solid var(--secondary-color); + border-radius: 4px; +} + +.checkbox-container label { + margin-bottom: 0; +} +.modal-body { + margin: 1.5rem 0; +} + +.intro-section { + display: flex; + gap: 1rem; + align-items: center; + justify-content: space-between; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + margin-top: 2rem; + gap: 1rem; + border-top: 1px solid rgba(0, 0, 0, 0.1); + padding-top: 1.5rem; +} + +#modal-title { + color: var(--primary-color); + margin: 0 0 1rem 0; + font-size: 1.5rem; + font-weight: 600; +} + +.modal button { + padding: 0.75rem 1.5rem; + border-radius: 6px; + font-size: 1rem; + cursor: pointer; + transition: all 0.2s ease; +} + +#save-settings { + background-color: var(--accent-color); + color: white; + border: none; +} + +#save-settings:hover { + background-color: #004494; +} + +#close-modal { + background-color: transparent; + border: 1px solid var(--primary-color); + color: var(--primary-color); +} + +#close-modal:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +/* Base styles for readability */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, + sans-serif; + line-height: 1.6; + color: var(--primary-color); + background-color: var(--background-color); + margin: 0; + padding: 20px; + font-size: 16px; + max-width: 800px; + margin: 0 auto; +} + +/* Skip link with enhanced visibility */ +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: var(--accent-color); + color: white; + padding: 8px; + z-index: 100; + transition: top 0.3s; +} + +.skip-link:focus { + top: 0; + outline: var(--focus-outline); + outline-offset: 2px; +} + +/* Enhanced focus styles */ +:focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; + border-radius: 2px; +} + +/* Progress bar styles */ +.quiz-progress { + margin: 2rem 0; +} +/* Images */ +.decorative-image, +.info-image { + max-width: 15rem; + height: auto; + margin: 1rem 0; + border-radius: 4px; +} + +.progress-bar { + background-color: var(--progress-bg); + border-radius: 8px; + height: 20px; + overflow: hidden; + position: relative; +} + +.progress-fill { + background-color: var(--accent-color); + height: 100%; + transition: width 0.3s ease; + border-radius: 8px; +} + +.progress-text { + margin-top: 0.5rem; + text-align: center; + font-weight: 500; +} + +/* Question groups */ +.question-group { + margin-bottom: 2rem; + padding: 1rem; + border: 1px solid #e2e8f0; + border-radius: 8px; + background-color: #f8fafc; +} + +.question-heading { + color: var(--primary-color); + margin-bottom: 1rem; + font-size: 1.1rem; +} + +/* Form elements */ +fieldset { + border: 1px solid var(--primary-color); + margin: 1em 0; + padding: 1em; + border-radius: 4px; +} + +legend { + font-weight: bold; + padding: 0.5em 1em; + color: var(--primary-color); + background-color: #f8fafc; + border: 1px solid var(--primary-color); + border-radius: 4px; +} + +.radio-group { + margin: 1em 0; +} + +/* Enhanced radio button styling */ +.radio-option { + position: relative; + padding: 0.75em; + margin: 0.5em 0; + border-radius: 4px; + transition: background-color 0.2s; + display: flex; + align-items: center; +} + +.radio-option:hover { + background-color: rgba(52, 152, 219, 0.1); +} + +/* Custom radio button styling */ +input[type='radio'] { + margin-right: 0.75em; + width: 20px; + height: 20px; +} + +input[type='radio']:focus-visible + label { + outline: var(--focus-outline); + outline-offset: 2px; + border-radius: 2px; +} + +input[type='radio'] + label { + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + transition: background-color 0.2s; + flex: 1; +} + +/* Instructions section */ +.quiz-instructions { + background-color: #f8f9fa; + padding: 1em; + margin: 1em 0; + border-left: 4px solid var(--accent-color); + border-radius: 0 4px 4px 0; +} + +/* Submit button */ +.form-controls { + margin-top: 2rem; + text-align: center; +} + +.submit-button { + background-color: var(--accent-color); + color: white; + border: none; + padding: 12px 24px; + font-size: 1rem; + cursor: pointer; + border-radius: 4px; + transition: background-color 0.2s, transform 0.1s; + font-weight: 500; +} + +.submit-button:hover { + background-color: #2980b9; +} + +.submit-button:active { + transform: scale(0.98); +} + +.submit-button:focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; +} + +/* Results section */ +#results { + margin-top: 2rem; + padding: 1.5rem; + border-radius: 8px; + background-color: #f8fafc; + border: 1px solid var(--primary-color); +} + +.feedback-details { + margin-top: 1rem; + padding: 1rem; + border-radius: 4px; +} + +/* Utility classes */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* High contrast mode */ +@media (prefers-contrast: more) { + :root { + --primary-color: #000000; + --accent-color: #0000ee; + --focus-outline: 3px solid #000000; + } + + header { + background: #ffffff; + border: 2px solid var(--primary-color); + } + + nav a { + background-color: transparent; + border: 1px solid currentColor; + } + + nav a:hover { + background-color: var(--primary-color); + } + + .radio-option:hover { + background-color: #000000; + color: #ffffff; + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + * { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + } +} + +/* Basic responsive design */ +@media (max-width: 600px) { + body { + padding: 10px; + } + + fieldset { + padding: 0.5em; + } + + header { + padding: 1.25rem; + } + + nav ul { + gap: 0.75rem; + } +} + +/* User Info Form */ +#user-info { + margin-bottom: 2rem; + padding: 1.5rem; + background-color: #f8fafc; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.form-control { + margin-bottom: 1.5rem; + position: relative; +} + +.form-control label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--primary-color); +} + +.form-control input { + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid var(--primary-color); + border-radius: 4px; + transition: border-color 0.2s, box-shadow 0.2s; + box-sizing: border-box; +} + +.form-control input:focus { + outline: none; + border-color: var(--accent-color); + box-shadow: 0 0 0 3px var(--focus-ring-color); +} + +.form-control input[aria-invalid='true'] { + border-color: var(--error-color); +} + +.form-control input[aria-invalid='true']:focus { + box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.25); +} + +.error { + color: var(--error-color); + font-size: 0.875rem; + margin-top: 0.25rem; +} + +.hint { + color: var(--secondary-color); + font-size: 0.875rem; + margin-top: 0.25rem; +} + +/* Introduction section and images */ +#introduction { + padding: 2rem; + margin-bottom: 2rem; + background-color: #f8fafc; + border-radius: 12px; +} + +#introduction h2 { + color: var(--primary-color); + font-size: 2rem; + margin-bottom: 1.5rem; +} + +#introduction p { + max-width: 800px; + margin: 0 auto 2rem; +} + +.hero-images { + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + margin: 2rem 0; +} + +.decorative-image, +.info-image { + max-width: 100%; + height: auto; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.info-image { + padding: 1rem; + background: white; +} + +@media (min-width: 768px) { + .hero-images { + flex-direction: row; + justify-content: center; + gap: 3rem; + } + + .decorative-image, + .info-image { + width: 45%; + max-width: 400px; + } +} + +/* Header and Navigation */ +header { + margin-bottom: 2rem; +} + +h1, +h2 { + margin-bottom: 1rem; +} + +nav ul { + list-style: none; + padding: 0; + display: flex; + gap: 1rem; +} + +nav a { + color: var(--accent-color); + text-decoration: none; +} + +nav a:hover { + text-decoration: underline; +} + +/* About page specific styles */ +#about { + padding: 3rem; + background-color: #f8fafc; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); +} + +#about h2 { + color: #2c3e50; + font-size: 2.5rem; + margin-bottom: 1.5rem; + border-bottom: 3px solid #3498db; + padding-bottom: 0.5rem; + display: inline-block; +} + +#about p { + font-size: 1.2rem; + line-height: 1.8; + color: #34495e; + margin-bottom: 2rem; + max-width: 700px; +} + +#about .hero-images { + margin: 3rem 0; + display: flex; + justify-content: center; +} + +#about .hero-images img { + max-width: 100%; + height: auto; + border-radius: 12px; + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease; +} + +#about .hero-images img:hover { + transform: scale(1.02); +} + +/* Header improvements */ +#header { + padding: 2rem 0; + margin-bottom: 3rem; + border-bottom: 1px solid #eaeaea; +} + +#header nav ul { + list-style: none; + padding: 0; + margin: 1rem 0; + display: flex; + gap: 2rem; +} + +#header nav a { + color: #0040a3; /* Darker blue for better contrast */ + text-decoration: none; + font-size: 1.1rem; + font-weight: 500; + padding: 0.5rem 1rem; + border-radius: 6px; + transition: all 0.2s ease; +} + +#header nav a:hover { + background-color: #0040a3; + color: white; + text-decoration: none; +} + +/* Responsive adjustments */ +@media (max-width: 600px) { + #about { + padding: 1.5rem; + } + + #about h2 { + font-size: 2rem; + } + + #about p { + font-size: 1.1rem; + } + + #header nav ul { + gap: 1rem; + } +} diff --git a/frontend/public/ReactHook.js b/frontend/public/ReactHook.js new file mode 100644 index 00000000..c67fc69d --- /dev/null +++ b/frontend/public/ReactHook.js @@ -0,0 +1,226 @@ +import React from 'react'; + +// Function component using function keyword +function FunctionComponent(props) { + return ( +
+

Hello, {props.name}!

+

This is a function component.

+
+ ); +} + +// Function component using arrow function syntax +const FunctionComponent = (props) => { + return ( +
+

Hello, {props.name}!

+

This is a function component.

+
+ ); +}; + +// Removed redundant default export +import React, { useState } from 'react'; + +const FunctionComponent = () => { + // Using useState Hook to manage state + const [count, setCount] = useState(0); + + return ( +
+

Count: {count}

+ +
+ ); +}; + +// Removed redundant default export +import React, { useState, useEffect } from 'react'; + +const FunctionComponent = () => { + const [count, setCount] = useState(0); + + // useEffect Hook to replicate componentDidMount and componentDidUpdate + useEffect(() => { + // This code block runs after every render + console.log("Component did mount or update"); + + // Clean-up function (replicating componentWillUnmount) + return () => { + console.log("Component will unmount"); + }; + }); + + return ( +
+

Count: {count}

+ +
+ ); +}; + +export { FunctionComponent }; +import React, { Component } from 'react'; + +// Define a class component that extends React.Component or React.PureComponent +class ClassComponent extends Component { + // Define constructor if necessary + constructor(props) { + super(props); + // Initialize state if needed + this.state = { + count: 0 + }; + } + + // Define lifecycle methods if necessary + componentDidMount() { + // Code to run after the component is mounted + } + + // Define instance methods if necessary + handleClick = () => { + // Update state or perform other logic + this.setState({ count: this.state.count + 1 }); + } + + // Define render() method to return JSX + render() { + return ( +
+

Count: {this.state.count}

+ +
+ ); + } +} + +// Removed redundant default export +import React, { Component } from 'react'; + +class ClassComponent extends Component { + constructor(props) { + super(props); + // Initialize state + this.state = { + count: 0 + }; + } + + // Define a method to update state + incrementCount = () => { + // Use this.setState() to update state + this.setState({ count: this.state.count + 1 }); + } + + render() { + return ( +
+

Count: {this.state.count}

+ +
+ ); + } +} + +export { ClassComponent }; +import React, { Component } from 'react'; + +class ClassComponent extends Component { + constructor(props) { + super(props); + this.state = { + data: null + }; + } + + componentDidMount() { + // Fetch initial data when the component mounts + this.fetchData(); + } + + componentDidUpdate(prevProps, prevState) { + // Check if the data has changed + if (prevState.data !== this.state.data) { + // Data has changed, perform additional actions + console.log('Data has been updated:', this.state.data); + } + } + + componentWillUnmount() { + // Cleanup tasks before the component is unmounted + console.log('Component will unmount'); + // For example, remove event listeners, cancel ongoing tasks, etc. + } + + fetchData() { + // Simulate fetching data from an API + setTimeout(() => { + this.setState({ data: 'Some data fetched from API' }); + }, 1000); + } + + render() { + return ( +
+

Data: {this.state.data}

+
+ ); + } +} + +import React, { Component } from 'react'; + +class ClassComponent extends Component { + constructor(props) { + super(props); + this.state = { + count: 0 + }; + } + + // Custom method to handle incrementing count + handleIncrement = () => { + this.setState({ count: this.state.count + 1 }); + } + + // Custom method to handle decrementing count + handleDecrement = () => { + this.setState({ count: this.state.count - 1 }); + } + + render() { + return ( +
+

Count: {this.state.count}

+ + +
+ ); + } +} + +export { ClassComponent }; +import React, { useState } from 'react'; + +function Counter() { + const [count, setCount] = useState(0); + + return ( +
+

Count: {count}

+ +
+ ); +} +import { useTheme } from 'styled-components' + + +const MyComponent = () => { + const theme = useTheme() + + + console.log('Current theme: ', theme) + // ... +} \ No newline at end of file diff --git a/frontend/public/about.html b/frontend/public/about.html new file mode 100644 index 00000000..38a11eb8 --- /dev/null +++ b/frontend/public/about.html @@ -0,0 +1,68 @@ + + + + + + About Us + + + + +
+

About Us

+ +
+
+
+

About Our Website Feedback Initiative

+

+ We are committed to making our website accessible to everyone. Your feedback helps us improve and create a better experience for all users. +

+
+ An illustration representing digital accessibility, showing a person in a wheelchair interacting with a computer. +
+
+

Accessibility Features

+
+

+ +

+ +
+
+

+ +

+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/frontend/public/accordion.js b/frontend/public/accordion.js new file mode 100644 index 00000000..273570d1 --- /dev/null +++ b/frontend/public/accordion.js @@ -0,0 +1,66 @@ +function initAccordion(){ + const accordion = document.querySelector('accordion'); + if (!accordion) return; + + const buttons = accordion.querySelectorAll('accordion-button'); + + buttons.forEach((button, index) => { + button.addEventListener('keydown', (event) => { + const targetId = button.getAttribute('aria-controls'); + const targetPanel = document.getElementById(targetId); + + switch (event.key){ + case 'ArrowDown': + case 'ArrowRight': + event.preventDefault(); + const nextButton = buttons[(index + 1) % buttons.length]; + nextButton.focus(); + break; + case 'ArrowUp': + case 'ArrowLeft': + event.preventDefault(); + case 'ArrowUp': + case 'ArrowLeft': + event.preventDefault(); + const prevButton = buttons[(index - 1 + buttons.length) % buttons.length]; + prevButton.focus(); + break; + case 'Home': + event.preventDefault(); + buttons[0].focus(); + break; + case 'End': + event.preventDefault(); + buttons[buttons.length - 1].focus(); + break; + case ' ': + case 'Enter': + event.preventDefault(); + togglePanel(button, targetPanel); + break; + } +}); +button.addEventListener('click', () =>{ + const targetId = button .getAttribute('aria-controls'); + const targetPanel = document.getElementById(targetId); +}); +}); + +function togglePanel(button, panel) { +const isExpanded = button.getAttribute('aria-expanded') === 'true'; +const newExpandedState = !isExpanded; +button.setAttribute('aria-expanded', newExpandedState); +if (newExpandedState) { + panel.hidden =false; + requestAnimationFrame(() => { + panel.style.maxHeight = panel.scrollHeight + 'px'; + }); +} else { + panel.addEventListener('transitionend', () => { + panel.hidden = true; + }, { once: true }); + } +} + +document.addEventListener('DOMContentLoaded', initAccordion); +} diff --git a/frontend/public/backend/server.js b/frontend/public/backend/server.js new file mode 100644 index 00000000..484619a9 --- /dev/null +++ b/frontend/public/backend/server.js @@ -0,0 +1,136 @@ +import express from 'express' +import bodyParser from 'body-parser' +import cors from 'cors' +import crypto from 'crypto' +import mongoose from 'mongoose' +import bcrypt from 'bcrypt-nodejs' + +const mongoUrl =process.env_MONGO_URL II "mongodb://localhost/auth" +mongoose.connect(mongoUrl, { useNewUrlParser: true, useundefinedtopology: true}) +mongoose.Promise = Promise + +const { Schema, model } = mongoose +const userSchema = new Schema({ + +const User = mongoose.model('User', { + name: { + type: String, + unique: true + }, + password:{ + type:String, + required:true + }, + accessToken:{ + type:String, + default: () => crypto.randomBytes(128)toString('hex') + } + }); + const User = model("User", userSchema) + + // Example + // POST Request + const request = {name: "Bob", password: "foobar}; + // DB Entry + const dbEntry = {name :"Bob, password:"5abbc32983def"} + bcrypt.compareSync(request.password, dbEntry.password); + // One-way encryption +const user = new User({name:"Bob", password:bcrypt.hashSync("foobar")}); +user.save(); +// Defines the port the app will run on. Defaults to 8080, but can be overritten when starting the server. For example: +// +// PORT-9000 npm start +const port = process.env.PORT II 8080 +const app = express() +const authenticateUser = async (req, res, next) => { + const user = await User.findOne({accesToken: req.header('Authorization')}); + if(user){ + req.user = user; + next(); + {else{ +res.status(401).json({loggedOut:true}); + }} + } +} + +// Add middlewares to enable cors and json body parsing +app.use(cors()) +app.use(express.json()); +app.use(bodyParser.json()) + +// Start defining your routes here +app.get('/', (req, res) => { + res.send('Hello Member') + app.post('/tweets' authenticateUser); + app.post('/tweets', async (req,res) =>{ + // This will only happen if the next() function is called from middleware! + // now we can access the req.user object from the middleware + }) + + } + }) +}) + +// Start defining your routes here +app.get('/,(req, res) => { + res.send('Hello world') +}) +app.post('/sessions' async (req res) => { + const user = await User.findOne({name: req.body.name}); + if(user && bcrypt.compareSync(req.body.password, user.password)){ + // Success + res.json({userId: user._id, accessToken}}; + }else{ + // Failure + // a.User does not exist + // b. Encrypted password does not march + res.json({notFound: true}); + } + } +}); +app.post("/users", (req, res) => { + try { + const { name, email, password } = req.body + const salt = bcrypt.genSaltSync() + const user = new User({ name, email, password: bcrypt.hashSync(password, salt) }) + user.save() + res.status(201).json({ + success: true, + message: "User created", + id: user._id, + accessToken: user.accessToken, + }) + } catch (error) { + res.status(400).json({ + success: false, + message: "Could not create user", + errors: error + }) + } + }) + + app.get("/secrets", authenticateUser) +app.get("/secrets", (req, res) => { + res.json({ + secret: "This is secret" + app.get('/secrets', (req, res) =>{ + res.jsons({secret: 'This is a super secret message'}) + }); + } + app.post('/sessions' async (req, res) => { + const user = await User.findOne({email: req.body.email}); + if (user && bcrypt.compareSync(req.body.password. user.password)){ + res.json({userId: user_Id, assessToken: user.accessToken}); + }else{ + res.json({notFound: true}); + + + + +// Start the server +}) +app.listen(port, () => { + console.log('Server running on http://localhost:${port}') +}) + console.log(bcrypt.hashSync("foobar")); +}) \ No newline at end of file diff --git a/frontend/public/backend/settings.json b/frontend/public/backend/settings.json new file mode 100644 index 00000000..03adc8d2 --- /dev/null +++ b/frontend/public/backend/settings.json @@ -0,0 +1,4 @@ +{ + "IDX.aI.enableInlineCompletion": true, + "IDX.aI.enableCodebaseIndexing": true +} \ No newline at end of file diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 00000000..86d6cc3f --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,115 @@ + + + + + + Website Feedback + + + + +
+
+

Website Feedback

+ +
+
+ +
+
+

Introduction

+

We value your feedback! Help us improve our website by sharing your experience.

+
+ +
+
+ + + + + +
+ + + +
+ + + + + + \ No newline at end of file diff --git a/frontend/public/johanska b/frontend/public/johanska new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/keyboardnavigation.js b/frontend/public/keyboardnavigation.js new file mode 100644 index 00000000..ba6123a7 --- /dev/null +++ b/frontend/public/keyboardnavigation.js @@ -0,0 +1,41 @@ +// JavaScript for keyboard navigation +Element.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Enter': + case ' ': + // Activate the element + break; + case 'ArrowRight': + case 'ArrowDown': + // Move to next item + break; + case 'ArrowLeft': + case 'ArrowUp': + // Move to previous item + break; + case 'Home': + // Move to first item + break; + case 'End': + // Move to last item + break; + } +}); + +// Focus Management + +// After form submission +submitButton.addEventListener('click', () => { + // Hide form + FormData.hidden = true; + + // Show results + SpeechRecognitionResultList.hidden = false; + + // Set focus to results + SpeechRecognitionResultList.setAttribute('tabindex', '-1'); + SpeechRecognitionResultList.focus(); + + // Announce to screen readers + announcer.textContent = 'Form submitted successfully'; +}); \ No newline at end of file diff --git a/frontend/public/main.js b/frontend/public/main.js new file mode 100644 index 00000000..c54336a3 --- /dev/null +++ b/frontend/public/main.js @@ -0,0 +1,156 @@ +document.addEventListener('DOMContentLoaded', () => { + const introSection = document.getElementById('user-info'); + const announcer = document.getElementById('announcer'); + + const introContinueButton = document.getElementById('intro-continue'); + +introContinueButton.addEventListener('click', () => { + introSection.hidden = true; + const userSection = document.getElementById('user-info-section'); + userSection.hidden = false; + window.location.hash = '#user-info'; + document.getElementById('name').focus(); + announcer.textContent = 'Moved to user information section'; + }); +}); + +const userInfoForm = document.getElementById('user-info-form'); +const nameInput = document.getElementById('name'); +const emailInput = document.getElementById('email'); +const emailError = document.getElementById('email-error'); +const nameError = document.getElementById('name-error'); +let userName = ''; + +function clearError(input, errorElement) { + input.removeAttribute('aria-invalid'); + errorElement.textContent = ''; + errorElement.hidden = true; +} + +function isValidEmail(email) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); +} + +function showError(input, errorElement, message) { + input.setAttribute('aria-invalid', 'true'); + errorElement.textContent = message; + errorElement.hidden = false; +} + +nameInput.addEventListener('input', () => { + if (nameInput.value.trim()) { + clearError(nameInput, nameError); + } +}); + +emailInput.addEventListener('input', () => { + if (emailInput.value.trim() && isValidEmail(emailInput.value)) { + clearError(emailInput, emailError); + } +}); + +userInfoForm.addEventListener('submit', (e) => { + e.preventDefault(); + let isValid = true; + + if (!nameInput.value.trim()) { + showError(nameInput, nameError, 'Please enter your name'); + isValid = false; + nameInput.focus(); + } else { + clearError(nameInput, nameError); + } + + if (!emailInput.value.trim()) { + showError(emailInput, emailError, 'Please enter your email address'); + isValid = false; + if (!nameError.textContent) { + emailInput.focus(); + } + } else if (!isValidEmail(emailInput.value.trim())) { + showError(emailInput, emailError, 'Please enter a valid email address'); + isValid = false; + if (!nameError.textContent) { + emailInput.focus(); + } + } else { + clearError(emailInput, emailError); + } + + if (isValid) { + userName = nameInput.value.trim(); + const userInfoSection = document.getElementById('user-info-section'); + const feedbackSection = document.getElementById('feedback-section'); + userInfoSection.hidden = true; + feedbackSection.hidden = false; + feedbackSection.scrollIntoView({ behavior: 'smooth' }); + document.querySelector('#feedback-form input[type="radio"]').focus(); + } +}); + +const feedbackSection = document.getElementById('feedback-form'); +const resultsSection = document.getElementById('results'); +const form = document.getElementById('feedback-form'); + const resultsContent =document.getElementById('results-content') + const feedbackDetails =document.querySelector('feedback-details') +const progressFill =document.querySelector('progress-fill') + const progressText = document.querySelector('.progress-text') + let answeredQuestions = new Set(); + +function updateProgress(){ + const totalQuestion =2; +const answeredCount = answeredQuestions.size; +const percentage = (answeredCount / totalQuestion) * 100; + +progressFill.style.width = `${percentage}%`; +progressText.textContent = `${answeredCount} of ${totalQuestion} sections completed`; +announcer.textContent = `${answeredCount} of ${totalQuestion} sections completed`; +} +form.querySelectorAll('input[type="radio"]').forEach((radio) => { + radio.addEventListener('change', () => { + const questionName = radio.name; + answeredQuestions.add(questionName); + updateProgress(); + }); +}); +form.addEventListener('submit', (e) => { + e.preventDefault(); + + const formData = new FormData(form); + const userAnswers = Object.fromEntries(formData); + + let feedback = `Thank you for your feedback, ${userName}`; + + const detailedFeedback =[]; + +if (userAnswers.navigation === 'easy') { + detailedFeedback.push('Navigation: You found the site easy to navigate.'); +} else if (userAnswers.navigation === 'difficult') { + detailedFeedback.push('Navigation: You found the site difficult to navigate.'); +} + +if (userAnswers.readability === 'clear') { + detailedFeedback.push('Readability: Content is clear and readable.'); +} else if (userAnswers.readability === 'unclear') { + detailedFeedback.push('Readability: Content needs improvement.'); +} +const userAnswerValues = Object.values(userAnswers); +let positiveResponses = 0; + +for (const answer of userAnswerValues) { + if (answer === 'easy' || answer === 'clear') { + positiveResponses += 1; + } +} + const totalResponses = Object.keys(userAnswers).length; + const satisfactionPercentage = Math.round((positiveResponses / totalResponses) * 100); + feedback = `Based on your responses, you seem ${satisfactionPercentage}% satisfied with our website.`; + feedback += positiveResponses >= totalResponses / 2 + ? ' Thank you for your positive feedback!' + : ' We will work hard to improve.'; + + feedbackDetails.innerHTML = detailedFeedback.map((text) => `

${text}

`).join(''); + resultsSection.setAttribute('tabindex', '-1'); + resultsSection.focus(); + announcer.textContent = 'Feedback submitted. Your results are now displayed.'; +}); \ No newline at end of file diff --git a/frontend/public/style.accessibility.css b/frontend/public/style.accessibility.css new file mode 100644 index 00000000..092764ef --- /dev/null +++ b/frontend/public/style.accessibility.css @@ -0,0 +1,640 @@ +* Base styles with accessibility considerations */ +:root { + --primary-color: #2c3e50; + --background-color: #ffffff; + --accent-color: #0040a3; /* Darker blue for better contrast */ + --error-color: #e74c3c; + --success-color: #27ae60; + --focus-outline: 3px solid var(--accent-color); + --focus-ring-color: rgba(0, 86, 179, 0.5); + --progress-bg: #edf2f7; + --modal-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); +} + +/* Apply box-sizing border-box to all elements */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* Modal Dialog */ +.modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(4px); +} + +/* Ensure hidden attribute works properly */ +.modal[hidden] { + display: none; +} + +.modal-content { + background-color: var(--background-color); + padding: 2rem; + border-radius: 8px; + max-width: 500px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + box-shadow: var(--modal-shadow); + border: 1px solid rgba(0, 0, 0, 0.1); + animation: modalAppear 0.3s ease-out; +} + +@keyframes modalAppear { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +/* Checkbox styling */ +.checkbox-container { + display: flex; + align-items: center; + margin-bottom: 0.5rem; +} + +.checkbox-container input[type='checkbox'] { + width: auto; + margin-right: 0.5rem; +} + +select { + width: 100%; + padding: 0.5rem; + font-size: 1rem; + border: 1px solid var(--secondary-color); + border-radius: 4px; +} + +.checkbox-container label { + margin-bottom: 0; +} +.modal-body { + margin: 1.5rem 0; +} + +.intro-section { + display: flex; + gap: 1rem; + align-items: center; + justify-content: space-between; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + margin-top: 2rem; + gap: 1rem; + border-top: 1px solid rgba(0, 0, 0, 0.1); + padding-top: 1.5rem; +} + +#modal-title { + color: var(--primary-color); + margin: 0 0 1rem 0; + font-size: 1.5rem; + font-weight: 600; +} + +.modal button { + padding: 0.75rem 1.5rem; + border-radius: 6px; + font-size: 1rem; + cursor: pointer; + transition: all 0.2s ease; +} + +#save-settings { + background-color: var(--accent-color); + color: white; + border: none; +} + +#save-settings:hover { + background-color: #004494; +} + +#close-modal { + background-color: transparent; + border: 1px solid var(--primary-color); + color: var(--primary-color); +} + +#close-modal:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +/* Base styles for readability */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, + sans-serif; + line-height: 1.6; + color: var(--primary-color); + background-color: var(--background-color); + margin: 0; + padding: 20px; + font-size: 16px; + max-width: 800px; + margin: 0 auto; +} + +/* Skip link with enhanced visibility */ +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: var(--accent-color); + color: white; + padding: 8px; + z-index: 100; + transition: top 0.3s; +} + +.skip-link:focus { + top: 0; + outline: var(--focus-outline); + outline-offset: 2px; +} + +/* Enhanced focus styles */ +:focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; + border-radius: 2px; +} + +/* Progress bar styles */ +.quiz-progress { + margin: 2rem 0; +} +/* Images */ +.decorative-image, +.info-image { + max-width: 15rem; + height: auto; + margin: 1rem 0; + border-radius: 4px; +} + +.progress-bar { + background-color: var(--progress-bg); + border-radius: 8px; + height: 20px; + overflow: hidden; + position: relative; +} + +.progress-fill { + background-color: var(--accent-color); + height: 100%; + transition: width 0.3s ease; + border-radius: 8px; +} + +.progress-text { + margin-top: 0.5rem; + text-align: center; + font-weight: 500; +} + +/* Question groups */ +.question-group { + margin-bottom: 2rem; + padding: 1rem; + border: 1px solid #e2e8f0; + border-radius: 8px; + background-color: #f8fafc; +} + +.question-heading { + color: var(--primary-color); + margin-bottom: 1rem; + font-size: 1.1rem; +} + +/* Form elements */ +fieldset { + border: 1px solid var(--primary-color); + margin: 1em 0; + padding: 1em; + border-radius: 4px; +} + +legend { + font-weight: bold; + padding: 0.5em 1em; + color: var(--primary-color); + background-color: #f8fafc; + border: 1px solid var(--primary-color); + border-radius: 4px; +} + +.radio-group { + margin: 1em 0; +} + +/* Enhanced radio button styling */ +.radio-option { + position: relative; + padding: 0.75em; + margin: 0.5em 0; + border-radius: 4px; + transition: background-color 0.2s; + display: flex; + align-items: center; +} + +.radio-option:hover { + background-color: rgba(52, 152, 219, 0.1); +} + +/* Custom radio button styling */ +input[type='radio'] { + margin-right: 0.75em; + width: 20px; + height: 20px; +} + +input[type='radio']:focus-visible + label { + outline: var(--focus-outline); + outline-offset: 2px; + border-radius: 2px; +} + +input[type='radio'] + label { + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + transition: background-color 0.2s; + flex: 1; +} + +/* Instructions section */ +.quiz-instructions { + background-color: #f8f9fa; + padding: 1em; + margin: 1em 0; + border-left: 4px solid var(--accent-color); + border-radius: 0 4px 4px 0; +} + +/* Submit button */ +.form-controls { + margin-top: 2rem; + text-align: center; +} + +.submit-button { + background-color: var(--accent-color); + color: white; + border: none; + padding: 12px 24px; + font-size: 1rem; + cursor: pointer; + border-radius: 4px; + transition: background-color 0.2s, transform 0.1s; + font-weight: 500; +} + +.submit-button:hover { + background-color: #2980b9; +} + +.submit-button:active { + transform: scale(0.98); +} + +.submit-button:focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; +} + +/* Results section */ +#results { + margin-top: 2rem; + padding: 1.5rem; + border-radius: 8px; + background-color: #f8fafc; + border: 1px solid var(--primary-color); +} + +.feedback-details { + margin-top: 1rem; + padding: 1rem; + border-radius: 4px; +} + +/* Utility classes */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* High contrast mode */ +@media (prefers-contrast: more) { + :root { + --primary-color: #000000; + --accent-color: #0000ee; + --focus-outline: 3px solid #000000; + } + + header { + background: #ffffff; + border: 2px solid var(--primary-color); + } + + nav a { + background-color: transparent; + border: 1px solid currentColor; + } + + nav a:hover { + background-color: var(--primary-color); + } + + .radio-option:hover { + background-color: #000000; + color: #ffffff; + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + * { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + } +} + +/* Basic responsive design */ +@media (max-width: 600px) { + body { + padding: 10px; + } + + fieldset { + padding: 0.5em; + } + + header { + padding: 1.25rem; + } + + nav ul { + gap: 0.75rem; + } +} + +/* User Info Form */ +#user-info { + margin-bottom: 2rem; + padding: 1.5rem; + background-color: #f8fafc; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.form-control { + margin-bottom: 1.5rem; + position: relative; +} + +.form-control label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--primary-color); +} + +.form-control input { + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid var(--primary-color); + border-radius: 4px; + transition: border-color 0.2s, box-shadow 0.2s; + box-sizing: border-box; +} + +.form-control input:focus { + outline: none; + border-color: var(--accent-color); + box-shadow: 0 0 0 3px var(--focus-ring-color); +} + +.form-control input[aria-invalid='true'] { + border-color: var(--error-color); +} + +.form-control input[aria-invalid='true']:focus { + box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.25); +} + +.error { + color: var(--error-color); + font-size: 0.875rem; + margin-top: 0.25rem; +} + +.hint { + color: var(--secondary-color); + font-size: 0.875rem; + margin-top: 0.25rem; +} + +/* Introduction section and images */ +#introduction { + padding: 2rem; + margin-bottom: 2rem; + background-color: #f8fafc; + border-radius: 12px; +} + +#introduction h2 { + color: var(--primary-color); + font-size: 2rem; + margin-bottom: 1.5rem; +} + +#introduction p { + max-width: 800px; + margin: 0 auto 2rem; +} + +.hero-images { + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + margin: 2rem 0; +} + +.decorative-image, +.info-image { + max-width: 100%; + height: auto; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.info-image { + padding: 1rem; + background: white; +} + +@media (min-width: 768px) { + .hero-images { + flex-direction: row; + justify-content: center; + gap: 3rem; + } + + .decorative-image, + .info-image { + width: 45%; + max-width: 400px; + } +} + +/* Header and Navigation */ +header { + margin-bottom: 2rem; +} + +h1, +h2 { + margin-bottom: 1rem; +} + +nav ul { + list-style: none; + padding: 0; + display: flex; + gap: 1rem; +} + +nav a { + color: var(--accent-color); + text-decoration: none; +} + +nav a:hover { + text-decoration: underline; +} + +/* About page specific styles */ +#about { + padding: 3rem; + background-color: #f8fafc; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); +} + +#about h2 { + color: #2c3e50; + font-size: 2.5rem; + margin-bottom: 1.5rem; + border-bottom: 3px solid #3498db; + padding-bottom: 0.5rem; + display: inline-block; +} + +#about p { + font-size: 1.2rem; + line-height: 1.8; + color: #34495e; + margin-bottom: 2rem; + max-width: 700px; +} + +#about .hero-images { + margin: 3rem 0; + display: flex; + justify-content: center; +} + +#about .hero-images img { + max-width: 100%; + height: auto; + border-radius: 12px; + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease; +} + +#about .hero-images img:hover { + transform: scale(1.02); +} + +/* Header improvements */ +#header { + padding: 2rem 0; + margin-bottom: 3rem; + border-bottom: 1px solid #eaeaea; +} + +#header nav ul { + list-style: none; + padding: 0; + margin: 1rem 0; + display: flex; + gap: 2rem; +} + +#header nav a { + color: #0040a3; /* Darker blue for better contrast */ + text-decoration: none; + font-size: 1.1rem; + font-weight: 500; + padding: 0.5rem 1rem; + border-radius: 6px; + transition: all 0.2s ease; +} + +#header nav a:hover { + background-color: #0040a3; + color: white; + text-decoration: none; +} + +/* Responsive adjustments */ +@media (max-width: 600px) { + #about { + padding: 1.5rem; + } + + #about h2 { + font-size: 2rem; + } + + #about p { + font-size: 1.1rem; + } + + #header nav ul { + gap: 1rem; + } +} diff --git "a/frontend/public/svg/Johan Skarpsv\303\244rd@2x.svg" "b/frontend/public/svg/Johan Skarpsv\303\244rd@2x.svg" new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/Arrow.svg b/frontend/public/svg/images/Arrow.svg new file mode 100644 index 00000000..e69de29b diff --git "a/frontend/public/svg/images/Johan Skarpsv\303\244rd.svg" "b/frontend/public/svg/images/Johan Skarpsv\303\244rd.svg" new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/Tablet_745.png b/frontend/public/svg/images/Tablet_745.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/divder.svg b/frontend/public/svg/images/divder.svg new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/icon-6931507.svg b/frontend/public/svg/images/icon-6931507.svg new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/imag-5.png b/frontend/public/svg/images/imag-5.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-1.png b/frontend/public/svg/images/img-1.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-10.png b/frontend/public/svg/images/img-10.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-11.png b/frontend/public/svg/images/img-11.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-2.png b/frontend/public/svg/images/img-2.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-3.png b/frontend/public/svg/images/img-3.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-4.png b/frontend/public/svg/images/img-4.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-5.png b/frontend/public/svg/images/img-5.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-6.png b/frontend/public/svg/images/img-6.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-7.png b/frontend/public/svg/images/img-7.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-8.png b/frontend/public/svg/images/img-8.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img-9.png b/frontend/public/svg/images/img-9.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/img.png b/frontend/public/svg/images/img.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/thesis.-1.png b/frontend/public/svg/images/thesis.-1.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/images/thesis.png b/frontend/public/svg/images/thesis.png new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/johan b/frontend/public/svg/johan new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/linkedin.svg b/frontend/public/svg/linkedin.svg new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/svg/vite.config.js b/frontend/public/svg/vite.config.js new file mode 100644 index 00000000..8d60bf7f --- /dev/null +++ b/frontend/public/svg/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) \ No newline at end of file diff --git a/public/vite.svg b/frontend/public/svg/vite.svg similarity index 100% rename from public/vite.svg rename to frontend/public/svg/vite.svg diff --git a/frontend/public/useeffect/.gitignore b/frontend/public/useeffect/.gitignore new file mode 100644 index 00000000..4d29575d --- /dev/null +++ b/frontend/public/useeffect/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/frontend/public/useeffect/README.md b/frontend/public/useeffect/README.md new file mode 100644 index 00000000..58beeacc --- /dev/null +++ b/frontend/public/useeffect/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in your browser. + +The page will reload when you make changes.\ +You may also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can't go back!** + +If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. + +You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `npm run build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/frontend/public/useeffect/package.json b/frontend/public/useeffect/package.json new file mode 100644 index 00000000..c2ecd5da --- /dev/null +++ b/frontend/public/useeffect/package.json @@ -0,0 +1,39 @@ +{ + "name": "useeffect", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^13.5.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-scripts": "5.0.1", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/frontend/public/useeffect/public/favicon.ico b/frontend/public/useeffect/public/favicon.ico new file mode 100644 index 00000000..a11777cc Binary files /dev/null and b/frontend/public/useeffect/public/favicon.ico differ diff --git a/frontend/public/useeffect/public/index.html b/frontend/public/useeffect/public/index.html new file mode 100644 index 00000000..aa069f27 --- /dev/null +++ b/frontend/public/useeffect/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/frontend/public/useeffect/public/logo192.png b/frontend/public/useeffect/public/logo192.png new file mode 100644 index 00000000..fc44b0a3 Binary files /dev/null and b/frontend/public/useeffect/public/logo192.png differ diff --git a/frontend/public/useeffect/public/logo512.png b/frontend/public/useeffect/public/logo512.png new file mode 100644 index 00000000..a4e47a65 Binary files /dev/null and b/frontend/public/useeffect/public/logo512.png differ diff --git a/frontend/public/useeffect/public/manifest.json b/frontend/public/useeffect/public/manifest.json new file mode 100644 index 00000000..080d6c77 --- /dev/null +++ b/frontend/public/useeffect/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/frontend/public/useeffect/public/robots.txt b/frontend/public/useeffect/public/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/frontend/public/useeffect/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/frontend/public/useeffect/src/App.css b/frontend/public/useeffect/src/App.css new file mode 100644 index 00000000..74b5e053 --- /dev/null +++ b/frontend/public/useeffect/src/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/frontend/public/useeffect/src/App.js b/frontend/public/useeffect/src/App.js new file mode 100644 index 00000000..37845757 --- /dev/null +++ b/frontend/public/useeffect/src/App.js @@ -0,0 +1,25 @@ +import logo from './logo.svg'; +import './App.css'; + +function App() { + return ( +
+
+ logo +

+ Edit src/App.js and save to reload. +

+ + Learn React + +
+
+ ); +} + +export default App; diff --git a/frontend/public/useeffect/src/App.test.js b/frontend/public/useeffect/src/App.test.js new file mode 100644 index 00000000..1f03afee --- /dev/null +++ b/frontend/public/useeffect/src/App.test.js @@ -0,0 +1,8 @@ +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/frontend/public/useeffect/src/index.css b/frontend/public/useeffect/src/index.css new file mode 100644 index 00000000..ec2585e8 --- /dev/null +++ b/frontend/public/useeffect/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + 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; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/frontend/public/useeffect/src/index.js b/frontend/public/useeffect/src/index.js new file mode 100644 index 00000000..d563c0fb --- /dev/null +++ b/frontend/public/useeffect/src/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/frontend/public/useeffect/src/logo.svg b/frontend/public/useeffect/src/logo.svg new file mode 100644 index 00000000..9dfc1c05 --- /dev/null +++ b/frontend/public/useeffect/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/useeffect/src/reportWebVitals.js b/frontend/public/useeffect/src/reportWebVitals.js new file mode 100644 index 00000000..5253d3ad --- /dev/null +++ b/frontend/public/useeffect/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/frontend/public/useeffect/src/setupTests.js b/frontend/public/useeffect/src/setupTests.js new file mode 100644 index 00000000..8f2609b7 --- /dev/null +++ b/frontend/public/useeffect/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/frontend/public/vite-project/.gitignore b/frontend/public/vite-project/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/frontend/public/vite-project/.gitignore @@ -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? diff --git a/frontend/public/vite-project/App.js b/frontend/public/vite-project/App.js new file mode 100644 index 00000000..99895235 --- /dev/null +++ b/frontend/public/vite-project/App.js @@ -0,0 +1,33 @@ +import { useEffect, useState } from 'react' +import logo from './logo.svg'; +import './App.css'; + +function App(){ + const [count, setCount] = useState(0); + const [decrement, setDecrement] = useState(100); +useEffect(() => { + console.log('mount'); +}); +return ( +
+ + +
+ logo +

+ Edit src/App.js and save to reload. +

+ + + +
+
+ ); +} + +export default App; diff --git a/frontend/public/vite-project/README.md b/frontend/public/vite-project/README.md new file mode 100644 index 00000000..3f5ef227 --- /dev/null +++ b/frontend/public/vite-project/README.md @@ -0,0 +1,17 @@ +# React + 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 using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. +A/RES/217(III) +A/RES/70/1 +GDPR EU 2016/679 +The AI Act +Copyright@Johan Skarpsvärd diff --git a/frontend/public/vite-project/api/Deatail.js b/frontend/public/vite-project/api/Deatail.js new file mode 100644 index 00000000..52ea1fc6 --- /dev/null +++ b/frontend/public/vite-project/api/Deatail.js @@ -0,0 +1,23 @@ +import React, { useState, useEffect } from 'react'; +import ReactDOM from "react-dom"; +export const Detail = (props) => { +const [details, setDetails] = useState(); +useEffect(() => { + fetch(props.url) + .then(res => res.json()) + .then(json => { + console.log(json) + setDetails(json); + }); +}); +return ( +
+

{props.name}

+ {details && ( +
{JSON.stringify(details, null, 2)}
+ )} + {details && johanskarpsvärd} +
+); +} + diff --git a/frontend/public/vite-project/api/import React, {use b/frontend/public/vite-project/api/import React, {use new file mode 100644 index 00000000..e69de29b diff --git a/frontend/public/vite-project/api/index.js b/frontend/public/vite-project/api/index.js new file mode 100644 index 00000000..d2a3eec8 --- /dev/null +++ b/frontend/public/vite-project/api/index.js @@ -0,0 +1,30 @@ +import React, { useState, useEffect } from "react"; +import ReactDOM from "react-dom"; + + const App = () => { + const [johanSkarpsvard, setJohanSkarpsvard] = useState([]); + const [selectedJohanSkarpsvard, setSelectedJohanSkarpsvard] = useState([]); + useEffect(() => { + fetch("https://johanSkarpsvard.api") + .then(res => res.json()) + .then(json => setJohanSkarpsvard(json.results)); + }, []); + + return ( +
+
    + {johanSkarpsvard.map((johanskarpsvärd) => ( +
  • + {johanskarpsvärd.name} + +
  • + ))} +
+ {selectedJohanSkarpsvard && } +
+ ); + }; + const rootElement = document.getElementById("root"); + ReactDOM.render(, rootElement); diff --git a/frontend/public/vite-project/eslint.config.js b/frontend/public/vite-project/eslint.config.js new file mode 100644 index 00000000..ec2b712d --- /dev/null +++ b/frontend/public/vite-project/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/frontend/public/vite-project/index.html b/frontend/public/vite-project/index.html new file mode 100644 index 00000000..0c589ecc --- /dev/null +++ b/frontend/public/vite-project/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/frontend/public/vite-project/index.js b/frontend/public/vite-project/index.js new file mode 100644 index 00000000..344bbbe2 --- /dev/null +++ b/frontend/public/vite-project/index.js @@ -0,0 +1,21 @@ +import React from "react"; +import ReactDOM from "react-dom"; + +import "./style.accessibility.css"; + +const App = () => { + const [name, setName] = React.useState(''); + return ( +
event.preventDefault()}> +

The state is: {name}

+ setName(event.target.value)} + value={name} + /> +
+ ); +}; + +const rootElement = document.getElementById("root"); +ReactDOM.render(, rootElement); \ No newline at end of file diff --git a/frontend/public/vite-project/package.json b/frontend/public/vite-project/package.json new file mode 100644 index 00000000..82d8df17 --- /dev/null +++ b/frontend/public/vite-project/package.json @@ -0,0 +1,29 @@ +{ + "name": "vite-project", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "test": "cypress open", + "preview": "vite preview" + }, + "dependencies": { + "cypress": "^14.3.0", + "react": "^19.0.0", + "react-dom": "^19.0.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", + "vite": "^6.2.0" + } +} diff --git a/frontend/public/vite-project/public/components/card.css b/frontend/public/vite-project/public/components/card.css new file mode 100644 index 00000000..40de99b1 --- /dev/null +++ b/frontend/public/vite-project/public/components/card.css @@ -0,0 +1,5 @@ +article { + border: 2px solid black; + padding: 1rem; + width: 400px; + } \ No newline at end of file diff --git a/frontend/public/vite-project/public/components/card.jsx b/frontend/public/vite-project/public/components/card.jsx new file mode 100644 index 00000000..3cc744ce --- /dev/null +++ b/frontend/public/vite-project/public/components/card.jsx @@ -0,0 +1,42 @@ +// const Card = () =>
My profile card component
+ +// const Card = () => ( +//
+//

My profile card

+//
+// ) + +// const Card = () => { +// return ( +//
+//

My profile card

+//
+// ) +// } + +// const Card = () => { +// return ( +//
+//

Johan Skarpsvärd

+//
+// ) +// } + +// const Card = (props) => { +// return ( +//
+//

{props.title}

+//
+// ) +// } + +const Card = ({ title, text }) => { + return ( +
+

{title}

+

{text}

+
+ ) + } + + export default Card \ No newline at end of file diff --git a/frontend/public/vite-project/public/vite.svg b/frontend/public/vite-project/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/frontend/public/vite-project/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/vite-project/src/App.css b/frontend/public/vite-project/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/frontend/public/vite-project/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.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; +} diff --git a/frontend/public/vite-project/src/App.jsx b/frontend/public/vite-project/src/App.jsx new file mode 100644 index 00000000..f67355ae --- /dev/null +++ b/frontend/public/vite-project/src/App.jsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + React

+
+ +

+ Edit src/App.jsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/frontend/public/vite-project/src/assets/react.svg b/frontend/public/vite-project/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/frontend/public/vite-project/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/vite-project/src/index.css b/frontend/public/vite-project/src/index.css new file mode 100644 index 00000000..08a3ac9e --- /dev/null +++ b/frontend/public/vite-project/src/index.css @@ -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; + } +} diff --git a/src/main.jsx b/frontend/public/vite-project/src/main.jsx similarity index 85% rename from src/main.jsx rename to frontend/public/vite-project/src/main.jsx index ed109d76..b9a1a6de 100644 --- a/src/main.jsx +++ b/frontend/public/vite-project/src/main.jsx @@ -1,9 +1,7 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' - -import { App } from './App.jsx' - import './index.css' +import App from './App.jsx' createRoot(document.getElementById('root')).render( diff --git a/frontend/public/vite-project/vite.config.js b/frontend/public/vite-project/vite.config.js new file mode 100644 index 00000000..8b0f57b9 --- /dev/null +++ b/frontend/public/vite-project/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/src/App.jsx b/frontend/src/App.jsx similarity index 100% rename from src/App.jsx rename to frontend/src/App.jsx diff --git a/frontend/src/assets/stores/appContentStore.jsx b/frontend/src/assets/stores/appContentStore.jsx new file mode 100644 index 00000000..3f936b84 --- /dev/null +++ b/frontend/src/assets/stores/appContentStore.jsx @@ -0,0 +1,5 @@ +import {create} from "zustand" +export const appContentStore = create(() => ({})) +appContent:{ + heading: "State-Management-Zustand-js-project-portfolio-Global Tech Development" +} \ No newline at end of file diff --git a/frontend/src/assets/stores/components/Home.jsx b/frontend/src/assets/stores/components/Home.jsx new file mode 100644 index 00000000..0e555f46 --- /dev/null +++ b/frontend/src/assets/stores/components/Home.jsx @@ -0,0 +1,6 @@ +import {appContentStore} from "../stores/appContentStore" +export const Home () => { + const { appContent } = appContentStore() + return( +

{appContent.heading}

+} \ No newline at end of file diff --git a/frontend/src/assets/stores/components/pages/SignupForm b/frontend/src/assets/stores/components/pages/SignupForm new file mode 100644 index 00000000..8be5785b --- /dev/null +++ b/frontend/src/assets/stores/components/pages/SignupForm @@ -0,0 +1,52 @@ +import { useState } from "react"; + +const SignupForm = () => { + const [formData, setFormData] = useState({ + email: "", + password: "", + }); + + const handleSubmit = (e) => { + e.preventDefault(); + console.log(formData); + + fetch("http://localhost:8080/users", { + method: "POST", + body: JSON.stringify({ email: formData.email, password: formData.password }), + headers: { + "Content-Type": "application/json" + } + }) + .then(() => { + // Reset form + e.target.reset() + }) + .catch(error => { + console.log(error) + }) + + }; + + return ( +
+

SIGN UP

+ + setFormData({ ...formData, email: e.target.value })} + type="email" + name="email" + /> + + + setFormData({ ...formData, password: e.target.value })} + type="password" + name="password" + /> + + +
+ ); +}; + +export default SignupForm; \ No newline at end of file diff --git a/frontend/src/assets/stores/components/pages/UserSettings.jsx b/frontend/src/assets/stores/components/pages/UserSettings.jsx new file mode 100644 index 00000000..ef7fc4fc --- /dev/null +++ b/frontend/src/assets/stores/components/pages/UserSettings.jsx @@ -0,0 +1,17 @@ +import { useUserStore } from "../stores/userstore" +export const UserSettings = () => { + const { toggleLogin, } inCrementAge, setUserName} isLoggedIn, userName } = useUserStore() + return( +
+

User Settings

+

Logged in: {isLoggedIn ? "Yes" : "No"}

+ + + +
+ ) +} \ No newline at end of file diff --git a/frontend/src/assets/stores/components/pages/Userinfo.jsx b/frontend/src/assets/stores/components/pages/Userinfo.jsx new file mode 100644 index 00000000..19415b8c --- /dev/null +++ b/frontend/src/assets/stores/components/pages/Userinfo.jsx @@ -0,0 +1,11 @@ +import { useUserStore } from "../stores/userStore" +export const UserInfo = () => { + const { userName, age } = useUserStore() + +return ( +
+}

User Profile

+

User Name: {userName}>

+

Age: {age}

+
+)) \ No newline at end of file diff --git a/frontend/src/assets/stores/userStore.jsx b/frontend/src/assets/stores/userStore.jsx new file mode 100644 index 00000000..67e4a179 --- /dev/null +++ b/frontend/src/assets/stores/userStore.jsx @@ -0,0 +1,9 @@ +import { create } from "zustand" +export const useUserStore = create ((set) =>({ + isLoggedIn: false, + age: 25, + userName: "John Doe", + toggleLogin: () set ((state) => ({isLoggedIn:})) !state.isLoggedIn})) + incrementAge: () => set ((state) => ({age: state.age +1})), + setUserName: (newUserName) => set ({userName:}) +}) \ No newline at end of file diff --git a/src/data/projects.json b/frontend/src/data/projects.json similarity index 96% rename from src/data/projects.json rename to frontend/src/data/projects.json index 7c426028..462305ef 100644 --- a/src/data/projects.json +++ b/frontend/src/data/projects.json @@ -22,7 +22,7 @@ "APIs" ], "netlify": "link", - "github": "link" + "github": "link", } ] } \ No newline at end of file diff --git a/src/index.css b/frontend/src/index.css similarity index 100% rename from src/index.css rename to frontend/src/index.css diff --git a/gcrypt: b/gcrypt: new file mode 100644 index 00000000..e69de29b diff --git a/hello.cc: b/hello.cc: new file mode 100644 index 00000000..98fa1f05 --- /dev/null +++ b/hello.cc: @@ -0,0 +1,153 @@ +// hello.cc +#include + +namespace demo { + +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::NewStringType; +using v8::Object; +using v8::String; +using v8::Value; + +void Method(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + args.GetReturnValue().Set(String::NewFromUtf8( + isolate, "world", NewStringType::kNormal).ToLocalChecked()); +} + +void Initialize(Local exports) { + NODE_SET_METHOD(exports, "hello", Method); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) + +} // namespace demo +void Initialize(Local exports); +NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) +using namespace v8; + +extern "C" NODE_MODULE_EXPORT void +NODE_MODULE_INITIALIZER(Local exports, + Local module, + Local context) { + /* Perform addon initialization steps here. */ +} +static void DeleteInstance(void* data) { + // Cast `data` to an instance of the class and delete it. +} +#include + +using namespace v8; + +class AddonData { + public: + explicit AddonData(Isolate* isolate): + call_count(0) { + // Ensure this per-addon-instance data is deleted at environment cleanup. + node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this); + } + + // Per-addon data. + int call_count; + + static void DeleteInstance(void* data) { + delete static_cast(data); + } +}; + +static void Method(const v8::FunctionCallbackInfo& info) { + // Retrieve the per-addon-instance data. + AddonData* data = + reinterpret_cast(info.Data().As()->Value()); + data->call_count++; + info.GetReturnValue().Set((double)data->call_count); +} + +// Initialize this addon to be context-aware. +NODE_MODULE_INIT(/* exports, module, context */) { + Isolate* isolate = context->GetIsolate(); + + // Create a new instance of `AddonData` for this instance of the addon and + // tie its life cycle to that of the Node.js environment. + AddonData* data = new AddonData(isolate); + + // Wrap the data in a `v8::External` so we can pass it to the method we + // expose. + Local external = External::New(isolate, data); + + // Expose the method `Method` to JavaScript, and make sure it receives the + // per-addon-instance data we created above by passing `external` as the + // third parameter to the `FunctionTemplate` constructor. + exports->Set(context, + String::NewFromUtf8(isolate, "method").ToLocalChecked(), + FunctionTemplate::New(isolate, Method, external) + ->GetFunction(context).ToLocalChecked()).FromJust(); +} +void AddEnvironmentCleanupHook(v8::Isolate* isolate, + void (*fun)(void* arg), + void* arg); + // addon.cc +#include +#include +#include + +using node::AddEnvironmentCleanupHook; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Object; + +// Note: In a real-world application, do not rely on static/global data. +static char cookie[] = "yum yum"; +static int cleanup_cb1_called = 0; +static int cleanup_cb2_called = 0; + +static void cleanup_cb1(void* arg) { + Isolate* isolate = static_cast(arg); + HandleScope scope(isolate); + Local obj = Object::New(isolate); + assert(!obj.IsEmpty()); // assert VM is still alive + assert(obj->IsObject()); + cleanup_cb1_called++; +} + +static void cleanup_cb2(void* arg) { + assert(arg == static_cast(cookie)); + cleanup_cb2_called++; +} + +static void sanity_check(void*) { + assert(cleanup_cb1_called == 1); + assert(cleanup_cb2_called == 1); +} + +// Initialize this addon to be context-aware. +NODE_MODULE_INIT(/* exports, module, context */) { + Isolate* isolate = context->GetIsolate(); + + AddEnvironmentCleanupHook(isolate, sanity_check, nullptr); + AddEnvironmentCleanupHook(isolate, cleanup_cb2, cookie); + AddEnvironmentCleanupHook(isolate, cleanup_cb1, isolate); +} +// test.js +require('./build/Release/addon'); +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "hello.cc" ] + } + ] +} +// hello.js +const addon = require('./build/Release/addon'); + +console.log(addon.hello()); +// Prints: 'world' +try { + return require('./build/Release/addon.node'); +} catch (err) { + return require('./build/Debug/addon.node'); +} \ No newline at end of file diff --git a/index.html b/index.html index 6676fb2d..999aa6df 100644 --- a/index.html +++ b/index.html @@ -4,10 +4,18 @@ - Portfolio + Portfolio Tech New Developer Johan Skarpsvärd -
- - +
+ + + + + + + + + + diff --git a/input.js b/input.js new file mode 100644 index 00000000..984bedcf --- /dev/null +++ b/input.js @@ -0,0 +1,51 @@ +using handlerSync = openSync(); +await using handlerAsync = await openAsync(); +import data from "./data.json" with { type: "json" }; +// matches Aa and aa +const regex = /(?i:a)a/ +// matches aa, a\naa, etc. but not a\na +const regex = /(?m:^a)a/ +// matches \na and aa, but not \n\n +const regex = /(?s:.)./ +// replace this expression to `globalThis.foo = "top"` +this.foo = "top"; + +() => { + // replace + this.foo = "top" +} +function Foo() { + // don't replace + this.foo = "inner"; + } + + class Bar { + method() { + // don't replace + this.foo = "inner"; + } + } + class Bar { + // replace + [this.foo = "outer"]() { + // don't replace + this.foo = "inner"; + } + } + { + "type": "ClassMethod", // skipped + "key": { "type": "AssignmentExpression" }, // [this.foo = "outer"] + "body": { "type": "BlockStatement" }, // { this.foo = "inner"; } + "params": [], // should visit too if there are any + "computed": true + } + class Bar { + // replace + [this.foo = "outer"] = + // don't replace + this.foo + } + class Foo { + // replaced to `@globalThis.log` + @(this.log) foo = 1; + } \ No newline at end of file diff --git a/input.jsx b/input.jsx new file mode 100644 index 00000000..4e8d2ed2 --- /dev/null +++ b/input.jsx @@ -0,0 +1,6 @@ +const profile = ( +
+ +

{[user.firstName, user.lastName].join(" ")}

+
+ ); \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..407216e3 --- /dev/null +++ b/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e -o pipefail +shopt -s nocaseglob + +OUT_FILE=./autorestic + +# Type +NATIVE_OS=$(uname | tr '[:upper:]' '[:lower:]') +if [[ $NATIVE_OS == *"linux"* ]]; then + OS=linux +elif [[ $NATIVE_OS == *"darwin"* ]]; then + OS=darwin +elif [[ $NATIVE_OS == *"freebsd"* ]]; then + OS=freebsd +else + echo "Could not determine OS automatically, please check the release page manually: https://github.com/cupcakearmy/autorestic/releases" + exit 1 +fi +echo $OS + +NATIVE_ARCH=$(uname -m | tr '[:upper:]' '[:lower:]') +if [[ $NATIVE_ARCH == *"x86_64"* || $NATIVE_ARCH == *"amd64"* ]]; then + ARCH=amd64 +elif [[ $NATIVE_ARCH == *"arm64"* || $NATIVE_ARCH == *"aarch64"* ]]; then + ARCH=arm64 +elif [[ $NATIVE_ARCH == *"x86"* ]]; then + ARCH=386 +elif [[ $NATIVE_ARCH == *"armv7"* ]]; then + ARCH=arm +else + echo "Could not determine Architecure automatically, please check the release page manually: https://github.com/cupcakearmy/autorestic/releases" + exit 1 +fi +echo $ARCH + +if ! command -v bzip2 &>/dev/null; then + echo "Missing bzip2 command. Please install the bzip2 package for your system." + exit 1 +fi + +URL=$(wget -qO - https://api.github.com/repos/cupcakearmy/autorestic/releases/latest | grep "browser_download_url.*_${OS}_${ARCH}" | cut -d : -f 2,3 | tr -d \" | sed 's/ //g') +wget -qO "${OUT_FILE}.bz2" "$URL" +bzip2 -fd "${OUT_FILE}.bz2" +chmod +x ${OUT_FILE} + +./autorestic install +echo "Successfully installed autorestic" diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..37dc7e93 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,3 @@ +"transform": { + "^.+\\.jsx?$": "./path/to/wrapper.js" +}, \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..71a89149 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,201 @@ +/** + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +import type {Config} from 'jest'; + +const config: Config = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/tmp/jest_rs", + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "mts", + // "cts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + // testEnvironment: "jest-environment-node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.?([mc])[jt]s?(x)", + // "**/?(*.)+(spec|test).?([mc])[jt]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; + +export default config; diff --git a/js-project-portfolio/.gitignore b/js-project-portfolio/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/js-project-portfolio/.gitignore @@ -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? diff --git a/js-project-portfolio/README.md b/js-project-portfolio/README.md new file mode 100644 index 00000000..4866cf0f --- /dev/null +++ b/js-project-portfolio/README.md @@ -0,0 +1,73 @@ +# 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) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## 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 defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // 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, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +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 defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/js-project-portfolio/eslint.config.js b/js-project-portfolio/eslint.config.js new file mode 100644 index 00000000..b19330b1 --- /dev/null +++ b/js-project-portfolio/eslint.config.js @@ -0,0 +1,23 @@ +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' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/js-project-portfolio/index.html b/js-project-portfolio/index.html new file mode 100644 index 00000000..d9c743f1 --- /dev/null +++ b/js-project-portfolio/index.html @@ -0,0 +1,13 @@ + + + + + + + js-project-portfolio + + +
+ + + diff --git a/js-project-portfolio/package.json b/js-project-portfolio/package.json new file mode 100644 index 00000000..9eab9e1e --- /dev/null +++ b/js-project-portfolio/package.json @@ -0,0 +1,29 @@ +{ + "name": "js-project-portfolio", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^4.0.0", + "eslint": "^9.36.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.4.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.44.0", + "vite": "^6.3.6" + } +} diff --git a/js-project-portfolio/public/vite.svg b/js-project-portfolio/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/js-project-portfolio/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/js-project-portfolio/src/App.css b/js-project-portfolio/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/js-project-portfolio/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.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; +} diff --git a/js-project-portfolio/src/App.tsx b/js-project-portfolio/src/App.tsx new file mode 100644 index 00000000..3d7ded3f --- /dev/null +++ b/js-project-portfolio/src/App.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/js-project-portfolio/src/assets/react.svg b/js-project-portfolio/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/js-project-portfolio/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/js-project-portfolio/src/index.css b/js-project-portfolio/src/index.css new file mode 100644 index 00000000..08a3ac9e --- /dev/null +++ b/js-project-portfolio/src/index.css @@ -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; + } +} diff --git a/js-project-portfolio/src/main.tsx b/js-project-portfolio/src/main.tsx new file mode 100644 index 00000000..bef5202a --- /dev/null +++ b/js-project-portfolio/src/main.tsx @@ -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( + + + , +) diff --git a/js-project-portfolio/tsconfig.app.json b/js-project-portfolio/tsconfig.app.json new file mode 100644 index 00000000..a9b5a59c --- /dev/null +++ b/js-project-portfolio/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/js-project-portfolio/tsconfig.json b/js-project-portfolio/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/js-project-portfolio/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/js-project-portfolio/tsconfig.node.json b/js-project-portfolio/tsconfig.node.json new file mode 100644 index 00000000..7a77bab3 --- /dev/null +++ b/js-project-portfolio/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": [], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/js-project-portfolio/vite.config.ts b/js-project-portfolio/vite.config.ts new file mode 100644 index 00000000..8b0f57b9 --- /dev/null +++ b/js-project-portfolio/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/login.ejs b/login.ejs new file mode 100644 index 00000000..200208c1 --- /dev/null +++ b/login.ejs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + +

Sign in

+ \ No newline at end of file diff --git a/my-babel-plugin.js b/my-babel-plugin.js new file mode 100644 index 00000000..c998d14a --- /dev/null +++ b/my-babel-plugin.js @@ -0,0 +1,38 @@ +{ + "chrome": "60", + "opera": "47", + "edge": "79", + "firefox": "55", + "safari": "11.1", + "node": "8.3", + "deno": "1", + "ios": "11.3", + "samsung": "8", + "opera_mobile": "44", + "electron": "2.0" +} +import { isRequired } from "@babel/helper-compilation-targets"; + +module.exports = api => { + // Check if the targets have native object-rest-spread support + const objectRestSpreadSupported = !isRequired( + "transform-object-rest-spread", + api.targets() + ); +}; +import { + isIdentifierName, + isIdentifierStart, + isIdentifierChar, + isReservedWord, + isStrictBindOnlyReservedWord, + isStrictBindReservedWord, + isStrictReservedWord, + isKeyword, + } from "@babel/helper-validator-identifier"; + import environmentVisitor, { + requeueComputedKeyAndDecorators + } from "@babel/helper-environment-visitor"; + if (path.isMethod()) { + requeueComputedKeyAndDecorators(path) + } \ No newline at end of file diff --git a/my-project/.gitignore b/my-project/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/my-project/.gitignore @@ -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? diff --git a/my-project/README.md b/my-project/README.md new file mode 100644 index 00000000..7959ce42 --- /dev/null +++ b/my-project/README.md @@ -0,0 +1,69 @@ +# 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) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/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([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // 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, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +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([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/my-project/eslint.config.js b/my-project/eslint.config.js new file mode 100644 index 00000000..d94e7deb --- /dev/null +++ b/my-project/eslint.config.js @@ -0,0 +1,23 @@ +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' +import { globalIgnores } from 'eslint/config' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/my-project/index.html b/my-project/index.html new file mode 100644 index 00000000..e4b78eae --- /dev/null +++ b/my-project/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/my-project/package.json b/my-project/package.json new file mode 100644 index 00000000..685f0bd7 --- /dev/null +++ b/my-project/package.json @@ -0,0 +1,31 @@ +{ + "name": "my-project", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.12", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "tailwindcss": "^4.1.12" + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react": "^5.0.0", + "eslint": "^9.33.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.39.1", + "vite": "^7.1.2" + } +} diff --git a/my-project/public/vite.svg b/my-project/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/my-project/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/my-project/src/App.css b/my-project/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/my-project/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.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; +} diff --git a/my-project/src/App.tsx b/my-project/src/App.tsx new file mode 100644 index 00000000..3d7ded3f --- /dev/null +++ b/my-project/src/App.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/my-project/src/assets/react.svg b/my-project/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/my-project/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/my-project/src/error-page.jsx b/my-project/src/error-page.jsx new file mode 100644 index 00000000..17cb2e0c --- /dev/null +++ b/my-project/src/error-page.jsx @@ -0,0 +1,16 @@ +import { useRouteError } from "react-router-dom"; + +export default function ErrorPage() { + const error = useRouteError(); + console.error(error); + + return ( +
+

Oops!

+

Sorry, an unexpected error has occurred.

+

+ {error.statusText || error.message} +

+
+ ); +} \ No newline at end of file diff --git a/my-project/src/index.css b/my-project/src/index.css new file mode 100644 index 00000000..08a3ac9e --- /dev/null +++ b/my-project/src/index.css @@ -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; + } +} diff --git a/my-project/src/main.jsx b/my-project/src/main.jsx new file mode 100644 index 00000000..a78cf3c9 --- /dev/null +++ b/my-project/src/main.jsx @@ -0,0 +1,325 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom/client"; +import { + createBrowserRouter, + RouterProvider, +} from "react-router-dom"; +import "./index.css"; + +const router = createBrowserRouter([ + { + path: "/", + element:
Hello world!
, + }, +]); + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); +/* existing imports */ +import Root from "./routes/root"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + }, +]); + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); +/* previous imports */ +import ErrorPage from "./error-page"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + }, +]); + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); +/* existing imports */ +import Contact from "./routes/contact"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + }, + { + path: "contacts/:contactId", + element: , + }, +]); + +/* existing code */ +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + children: [ + { + path: "contacts/:contactId", + element: , + }, + ], + }, +]); +/* other imports */ +import Root, { loader as rootLoader } from "./routes/root"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + loader: rootLoader, + children: [ + { + path: "contacts/:contactId", + element: , + }, + ], + }, +]); +/* other imports */ + +import Root, { + loader as rootLoader, + action as rootAction, +} from "./routes/root"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + loader: rootLoader, + action: rootAction, + children: [ + { + path: "contacts/:contactId", + element: , + }, + ], + }, +]); +/* existing code */ +import Contact, { + loader as contactLoader, +} from "./routes/contact"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + loader: rootLoader, + action: rootAction, + children: [ + { + path: "contacts/:contactId", + element: , + loader: contactLoader, + }, + ], + }, +]); + +/* existing code */ +/* existing code */ +import EditContact from "./routes/edit"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + loader: rootLoader, + action: rootAction, + children: [ + { + path: "contacts/:contactId", + element: , + loader: contactLoader, + }, + { + path: "contacts/:contactId/edit", + element: , + loader: contactLoader, + }, + ], + }, +]); + +/* existing code */ +/* existing code */ +import EditContact, { + action as editAction, +} from "./routes/edit"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + loader: rootLoader, + action: rootAction, + children: [ + { + path: "contacts/:contactId", + element: , + loader: contactLoader, + }, + { + path: "contacts/:contactId/edit", + element: , + loader: contactLoader, + action: editAction, + }, + ], + }, +]); + +/* existing code */ +/* existing code */ +import { action as destroyAction } from "./routes/destroy"; + +const router = createBrowserRouter([ + { + path: "/", + /* existing root route props */ + children: [ + /* existing routes */ + { + path: "contacts/:contactId/destroy", + action: destroyAction, + }, + ], + }, +]); + +/* existing code */ +[ + /* other routes */ + { + path: "contacts/:contactId/destroy", + action: destroyAction, + errorElement:
Oops! There was an error.
, + }, +]; +// existing code +import Index from "./routes/index"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + loader: rootLoader, + action: rootAction, + children: [ + { index: true, element: }, + /* existing routes */ + ], + }, +]); +// existing code +import Contact, { + loader as contactLoader, + action as contactAction, +} from "./routes/contact"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + loader: rootLoader, + action: rootAction, + children: [ + { index: true, element: }, + { + path: "contacts/:contactId", + element: , + loader: contactLoader, + action: contactAction, + }, + /* existing code */ + ], + }, +]); +createBrowserRouter([ + { + path: "/", + element: , + loader: rootLoader, + action: rootAction, + errorElement: , + children: [ + { + errorElement: , + children: [ + { index: true, element: }, + { + path: "contacts/:contactId", + element: , + loader: contactLoader, + action: contactAction, + }, + /* the rest of the routes */ + ], + }, + ], + }, +]); +import { + createRoutesFromElements, + createBrowserRouter, + Route, +} from "react-router-dom"; + +const router = createBrowserRouter( + createRoutesFromElements( + } + loader={rootLoader} + action={rootAction} + errorElement={} + > + }> + } /> + } + loader={contactLoader} + action={contactAction} + /> + } + loader={contactLoader} + action={editAction} + /> + + + + ) +); diff --git a/my-project/src/main.tsx b/my-project/src/main.tsx new file mode 100644 index 00000000..e69de29b diff --git a/my-project/src/misc.ts b/my-project/src/misc.ts new file mode 100644 index 00000000..8a080b04 --- /dev/null +++ b/my-project/src/misc.ts @@ -0,0 +1,11 @@ +const findBareSupers = traverse.visitors.merge[]>([ + { + Super(path) { + const { node, parentPath } = path; + if (parentPath.isCallExpression({ callee: node })) { + this.push(parentPath); + } + }, + }, + environmentVisitor, + ]); \ No newline at end of file diff --git a/my-project/src/routes/contact.jsx b/my-project/src/routes/contact.jsx new file mode 100644 index 00000000..5dcce3ff --- /dev/null +++ b/my-project/src/routes/contact.jsx @@ -0,0 +1,259 @@ +import { Form } from "react-router-dom"; + +export default function Contact() { + const contact = { + first: "Your", + last: "Name", + avatar: "https://robohash.org/you.png?size=200x200", + twitter: "your_handle", + notes: "Some notes", + favorite: true, + }; + + return ( +
+
+ +
+ +
+

+ {contact.first || contact.last ? ( + <> + {contact.first} {contact.last} + + ) : ( + No Name + )}{" "} + +

+ + {contact.twitter && ( +

+ + {contact.twitter} + +

+ )} + + {contact.notes &&

{contact.notes}

} + +
+
+ +
+
{ + if ( + !confirm( + "Please confirm you want to delete this record." + ) + ) { + event.preventDefault(); + } + }} + > + +
+
+
+
+ ); +} + +function Favorite({ contact }) { + const favorite = contact.favorite; + return ( +
+ +
+ ); +} +import { Form, useLoaderData } from "react-router-dom"; +import { getContact } from "../contacts"; + +export async function loader({ params }) { + const contact = await getContact(params.contactId); + return { contact }; +} + +export default function Contact() { + const { contact } = useLoaderData(); + // existing code +} +
{ + if ( + !confirm( + "Please confirm you want to delete this record." + ) + ) { + event.preventDefault(); + } + }} +> + +
+import { + useLoaderData, + Form, + useFetcher, + } from "react-router-dom"; + + // existing code + + function Favorite({ contact }) { + const fetcher = useFetcher(); + const favorite = contact.favorite; + + return ( + + + + ); + } + // existing code +import { getContact, updateContact } from "../contacts"; + +export async function action({ request, params }) { + const formData = await request.formData(); + return updateContact(params.contactId, { + favorite: formData.get("favorite") === "true", + }); +} + +export default function Contact() { + // existing code +} +// existing code + +function Favorite({ contact }) { + const fetcher = useFetcher(); + + const favorite = fetcher.formData + ? fetcher.formData.get("favorite") === "true" + : contact.favorite; + + return ( + + + + ); + } + export async function loader({ params }) { + const contact = await getContact(params.contactId); + if (!contact) { + throw new Response("", { + status: 404, + statusText: "Not Found", + }); + } + return { contact }; + } + createBrowserRouter([ + { + path: "/", + element: , + loader: rootLoader, + action: rootAction, + errorElement: , + children: [ + { + errorElement: , + children: [ + { index: true, element: }, + { + path: "contacts/:contactId", + element: , + loader: contactLoader, + action: contactAction, + }, + /* the rest of the routes */ + ], + }, + ], + }, + ]); + import { + createRoutesFromElements, + createBrowserRouter, + Route, + } from "react-router-dom"; + + const router = createBrowserRouter( + createRoutesFromElements( + } + loader={rootLoader} + action={rootAction} + errorElement={} + > + }> + } /> + } + loader={contactLoader} + action={contactAction} + /> + } + loader={contactLoader} + action={editAction} + /> + + + + ) + ); + Copy code to clipboard + + \ No newline at end of file diff --git a/my-project/src/routes/destroy.jsx b/my-project/src/routes/destroy.jsx new file mode 100644 index 00000000..39a5bad3 --- /dev/null +++ b/my-project/src/routes/destroy.jsx @@ -0,0 +1,12 @@ +import { redirect } from "react-router-dom"; +import { deleteContact } from "../contacts"; + +export async function action({ params }) { + await deleteContact(params.contactId); + return redirect("/"); +} +export async function action({ params }) { + throw new Error("oh dang!"); + await deleteContact(params.contactId); + return redirect("/"); + } \ No newline at end of file diff --git a/my-project/src/routes/edit.jsx b/my-project/src/routes/edit.jsx new file mode 100644 index 00000000..02ad4608 --- /dev/null +++ b/my-project/src/routes/edit.jsx @@ -0,0 +1,115 @@ +import { Form, useLoaderData } from "react-router-dom"; + +export default function EditContact() { + const { contact } = useLoaderData(); + + return ( +
+

+ Name + + +

+ + +