Update logo, .gitignore, functions, index, and index.html files
|
@ -2,3 +2,4 @@ node_modules
|
|||
dist
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
build
|
|
@ -9,43 +9,17 @@ datasource db {
|
|||
extensions = [uuid_ossp(map: "uuid-ossp")]
|
||||
}
|
||||
|
||||
model articles {
|
||||
ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
|
||||
authorID String @db.Uuid
|
||||
title String @db.VarChar(100)
|
||||
views Int @default(0)
|
||||
public Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
author users @relation(fields: [authorID], references: [ID])
|
||||
sponsors sponsors[]
|
||||
}
|
||||
|
||||
model users {
|
||||
ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
|
||||
username String @db.VarChar(64)
|
||||
password String @db.VarChar(64)
|
||||
token String @db.VarChar(128)
|
||||
admin Boolean @default(false)
|
||||
article_create Boolean @default(false)
|
||||
article_manage Boolean @default(false)
|
||||
sponsor_manage Boolean @default(false)
|
||||
user_manage Boolean @default(false)
|
||||
|
||||
articles articles[]
|
||||
|
||||
@@unique([username])
|
||||
@@unique([token])
|
||||
ID String @id @default(dbgenerated("uuid_generate_v1()")) @db.Uuid
|
||||
username String
|
||||
password String @db.VarChar(128)
|
||||
token String @db.VarChar(128)
|
||||
}
|
||||
|
||||
model sponsors {
|
||||
ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
|
||||
name String @db.VarChar(12)
|
||||
url String @db.VarChar(128)
|
||||
description String @db.VarChar(100)
|
||||
|
||||
addedAt DateTime @default(now())
|
||||
|
||||
articles articles[]
|
||||
model Mitarbeiter {
|
||||
ID String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
|
||||
Vorname String
|
||||
Nachname String
|
||||
Anstelldatum DateTime @db.Date
|
||||
Geburtstag DateTime @db.Date
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 36 KiB |
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.3145608a.css",
|
||||
"main.js": "/static/js/main.20c1133d.js",
|
||||
"static/media/lexend-latin-wght-normal.woff2": "/static/media/lexend-latin-wght-normal.f0861a122355d47a9b27.woff2",
|
||||
"static/media/overpass-latin-wght-normal.woff2": "/static/media/overpass-latin-wght-normal.4b83544cf101ef6f5e10.woff2",
|
||||
"static/media/overpass-latin-ext-wght-normal.woff2": "/static/media/overpass-latin-ext-wght-normal.c7886930b5698c36c8d2.woff2",
|
||||
"static/media/lexend-latin-ext-wght-normal.woff2": "/static/media/lexend-latin-ext-wght-normal.94b395946a6799fff8c8.woff2",
|
||||
"static/media/overpass-cyrillic-wght-normal.woff2": "/static/media/overpass-cyrillic-wght-normal.5b31761fef8ac8222a1b.woff2",
|
||||
"static/media/overpass-cyrillic-ext-wght-normal.woff2": "/static/media/overpass-cyrillic-ext-wght-normal.08d0aad8582d63a917c6.woff2",
|
||||
"static/media/lexend-vietnamese-wght-normal.woff2": "/static/media/lexend-vietnamese-wght-normal.ac5494e335c7c2cb000d.woff2",
|
||||
"static/media/overpass-vietnamese-wght-normal.woff2": "/static/media/overpass-vietnamese-wght-normal.d1e91653b90562a59206.woff2",
|
||||
"index.html": "/index.html",
|
||||
"main.3145608a.css.map": "/static/css/main.3145608a.css.map",
|
||||
"main.20c1133d.js.map": "/static/js/main.20c1133d.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.3145608a.css",
|
||||
"static/js/main.20c1133d.js"
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 148 KiB |
After Width: | Height: | Size: 224 KiB |
After Width: | Height: | Size: 5.9 MiB |
After Width: | Height: | Size: 192 KiB |
|
@ -0,0 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>DWL Birthdays</title><script defer="defer" src="/static/js/main.20c1133d.js"></script><link href="/static/css/main.3145608a.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
After Width: | Height: | Size: 149 KiB |
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
|
@ -0,0 +1,2 @@
|
|||
body{margin:0;overflow-x:hidden;padding:0}body,label,span{font-family:Arial,Helvetica,sans-serif}@-webkit-keyframes gradientAnim{0%{background-position:0 50%}50%{background-position:100% 50%}to{background-position:0 50%}}@keyframes gradientAnim{0%{background-position:0 50%}50%{background-position:100% 50%}to{background-position:0 50%}}.nav-link{color:#005487;font-size:25px;font-weight:700;letter-spacing:.05em;line-height:24px;overflow:hidden;text-align:center;text-decoration:none;text-overflow:ellipsis;transition:all .3s ease 0s;white-space:nowrap;width:auto!important}.nav-link:after{background:#005487;content:"";display:block;height:2px;margin-left:auto;margin-right:auto;transition:width .3s ease 0s;width:0}.nav-link:hover:after{width:100%}.nav-link-left{color:#005487;font-size:25px;font-weight:700;letter-spacing:.05em;line-height:24px;overflow:hidden;text-align:center;text-decoration:none;text-overflow:ellipsis;transition:all .3s ease 0s;white-space:nowrap;width:auto!important}.nav-link-left:after{background:#fff;content:"";display:block;height:2px;margin-right:auto;transition:width .3s ease 0s;width:0}.nav-link-left:hover:after{width:100%}.se-resizing-bar{display:none!important}.sun-editor-editable{height:auto!important}.se-toolbar{position:-webkit-sticky!important;position:sticky!important;-webkit-transform:translate(-105px);transform:translate(-105px);width:100px!important;z-index:0!important}.se-btn-tray,.se-toolbar{flex-direction:column!important}.se-btn-tray{align-items:flex-start!important;display:flex!important;gap:2px!important}.se-wrapper{margin-top:-480px}.articleBanner{transition:.3s ease}.articleBanner:hover{-webkit-filter:brightness(.7);filter:brightness(.7)}a{color:#000;text-decoration:none}input[name=suneditor_image_radio]{display:none}@font-face{font-display:swap;font-family:Overpass Variable;font-style:normal;font-weight:100 900;src:url(/static/media/overpass-cyrillic-ext-wght-normal.08d0aad8582d63a917c6.woff2) format("woff2-variations");unicode-range:u+0460-052f,u+1c80-1c88,u+20b4,u+2de0-2dff,u+a640-a69f,u+fe2e-fe2f}@font-face{font-display:swap;font-family:Overpass Variable;font-style:normal;font-weight:100 900;src:url(/static/media/overpass-cyrillic-wght-normal.5b31761fef8ac8222a1b.woff2) format("woff2-variations");unicode-range:u+0301,u+0400-045f,u+0490-0491,u+04b0-04b1,u+2116}@font-face{font-display:swap;font-family:Overpass Variable;font-style:normal;font-weight:100 900;src:url(/static/media/overpass-vietnamese-wght-normal.d1e91653b90562a59206.woff2) format("woff2-variations");unicode-range:u+0102-0103,u+0110-0111,u+0128-0129,u+0168-0169,u+01a0-01a1,u+01af-01b0,u+0300-0301,u+0303-0304,u+0308-0309,u+0323,u+0329,u+1ea0-1ef9,u+20ab}@font-face{font-display:swap;font-family:Overpass Variable;font-style:normal;font-weight:100 900;src:url(/static/media/overpass-latin-ext-wght-normal.c7886930b5698c36c8d2.woff2) format("woff2-variations");unicode-range:u+0100-02af,u+0304,u+0308,u+0329,u+1e00-1e9f,u+1ef2-1eff,u+2020,u+20a0-20ab,u+20ad-20cf,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-display:swap;font-family:Overpass Variable;font-style:normal;font-weight:100 900;src:url(/static/media/overpass-latin-wght-normal.4b83544cf101ef6f5e10.woff2) format("woff2-variations");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+0304,u+0308,u+0329,u+2000-206f,u+2074,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-display:swap;font-family:Lexend Variable;font-style:normal;font-weight:100 900;src:url(/static/media/lexend-vietnamese-wght-normal.ac5494e335c7c2cb000d.woff2) format("woff2-variations");unicode-range:u+0102-0103,u+0110-0111,u+0128-0129,u+0168-0169,u+01a0-01a1,u+01af-01b0,u+0300-0301,u+0303-0304,u+0308-0309,u+0323,u+0329,u+1ea0-1ef9,u+20ab}@font-face{font-display:swap;font-family:Lexend Variable;font-style:normal;font-weight:100 900;src:url(/static/media/lexend-latin-ext-wght-normal.94b395946a6799fff8c8.woff2) format("woff2-variations");unicode-range:u+0100-02af,u+0304,u+0308,u+0329,u+1e00-1e9f,u+1ef2-1eff,u+2020,u+20a0-20ab,u+20ad-20cf,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-display:swap;font-family:Lexend Variable;font-style:normal;font-weight:100 900;src:url(/static/media/lexend-latin-wght-normal.f0861a122355d47a9b27.woff2) format("woff2-variations");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+0304,u+0308,u+0329,u+2000-206f,u+2074,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}
|
||||
/*# sourceMappingURL=main.3145608a.css.map*/
|
|
@ -0,0 +1,93 @@
|
|||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @remix-run/router v1.9.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router v6.16.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* [js-sha256]{@link https://github.com/emn178/js-sha256}
|
||||
*
|
||||
* @version 0.10.1
|
||||
* @author Chen, Yi-Cyuan [emn178@gmail.com]
|
||||
* @copyright Chen, Yi-Cyuan 2014-2023
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
//! moment.js
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Loading</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -11,8 +11,6 @@ dotenv.config()
|
|||
const prisma = new PrismaClient()
|
||||
const app = express()
|
||||
|
||||
fs.mkdirSync("/var/lib/rheinefuerrheine/artikel", { recursive: true })
|
||||
fs.mkdirSync("/var/lib/rheinefuerrheine/sponsoren", { recursive: true })
|
||||
|
||||
app.use(express.json({
|
||||
limit: '50mb'
|
||||
|
@ -25,13 +23,11 @@ app.use((req, res, next) => {
|
|||
next();
|
||||
})
|
||||
|
||||
app.use(express.static('public'))
|
||||
|
||||
export { prisma, app }
|
||||
init()
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send("You took the wrong turn, buddy.")
|
||||
})
|
||||
|
||||
app.post('/api/auth/login', async (req, res) => {
|
||||
const { username, password } = req.body
|
||||
|
||||
|
@ -65,7 +61,7 @@ app.get('/api/auth/verify', async (req, res) => {
|
|||
res.send('OK')
|
||||
})
|
||||
|
||||
app.post("/api/article/create", async (req, res) => {
|
||||
app.post("/api/worker/create", async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
|
||||
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
@ -77,31 +73,9 @@ app.post("/api/article/create", async (req, res) => {
|
|||
if (!title || !content || !image) return res.status(400).send(Errors.MISSING_ITEMS)
|
||||
|
||||
// sponsors is an array of sponsor IDs
|
||||
|
||||
const article = await prisma.articles.create({
|
||||
data: {
|
||||
title,
|
||||
public: true,
|
||||
author: {
|
||||
connect: {
|
||||
ID: user.ID
|
||||
}
|
||||
},
|
||||
sponsors: {
|
||||
connect: sponsors.map((sponsor: string) => ({
|
||||
ID: sponsor
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
fs.writeFileSync(`/var/lib/rheinefuerrheine/artikel/${article.ID}.html`, content)
|
||||
fs.writeFileSync(`/var/lib/rheinefuerrheine/artikel/${article.ID}.banner`, image);
|
||||
|
||||
res.send(article)
|
||||
})
|
||||
|
||||
app.post("/api/article/edit/:id", async (req, res) => {
|
||||
app.get('/api/workers', async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
|
||||
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
@ -109,317 +83,16 @@ app.post("/api/article/edit/:id", async (req, res) => {
|
|||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const { title, content, image, sponsors } = req.body
|
||||
if (!title || !content || !image || !req.params.id) return res.status(400).send(Errors.MISSING_ITEMS)
|
||||
|
||||
// sponsors is an array of sponsor IDs
|
||||
|
||||
const article = await prisma.articles.update({
|
||||
where: {
|
||||
ID: req.params.id
|
||||
},
|
||||
data: {
|
||||
title,
|
||||
sponsors: {
|
||||
set: sponsors.map((sponsor: string) => ({
|
||||
ID: sponsor
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
fs.writeFileSync(`/var/lib/rheinefuerrheine/artikel/${article.ID}.html`, content)
|
||||
if (image !== "stale") fs.writeFileSync(`/var/lib/rheinefuerrheine/artikel/${article.ID}.banner`, image);
|
||||
|
||||
res.send(article)
|
||||
})
|
||||
|
||||
app.get('/api/article/view/:id', async (req, res) => {
|
||||
const article = await prisma.articles.update({
|
||||
where: {
|
||||
ID: req.params.id,
|
||||
},
|
||||
data: {
|
||||
views: {
|
||||
increment: 1
|
||||
}
|
||||
},
|
||||
const workers = await prisma.mitarbeiter.findMany({
|
||||
select: {
|
||||
ID: true,
|
||||
title: true,
|
||||
views: true,
|
||||
author: {
|
||||
select: {
|
||||
ID: true,
|
||||
username: true
|
||||
}
|
||||
},
|
||||
sponsors: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
}
|
||||
}).catch(() => null)
|
||||
|
||||
if (!article) return res.status(404).send(Errors.NOT_FOUND)
|
||||
|
||||
res.send(article)
|
||||
})
|
||||
|
||||
app.get("/api/article/content/:id", async (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
res.sendFile(`/var/lib/rheinefuerrheine/artikel/${req.params.id}.html`)
|
||||
})
|
||||
|
||||
app.get("/api/allArticles", async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
|
||||
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const articles = await prisma.articles.findMany({
|
||||
select: {
|
||||
ID: true,
|
||||
title: true,
|
||||
views: true,
|
||||
author: {
|
||||
select: {
|
||||
ID: true,
|
||||
username: true
|
||||
}
|
||||
},
|
||||
sponsors: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
...((!user.admin && !user.article_manage) && {
|
||||
where: {
|
||||
authorID: user.ID
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
res.send(articles)
|
||||
})
|
||||
|
||||
app.get('/api/articles/public', async (req, res) => {
|
||||
const articles = await prisma.articles.findMany({
|
||||
select: {
|
||||
ID: true,
|
||||
title: true,
|
||||
views: true,
|
||||
author: {
|
||||
select: {
|
||||
ID: true,
|
||||
username: true
|
||||
}
|
||||
},
|
||||
sponsors: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
where: {
|
||||
public: true
|
||||
Vorname: true,
|
||||
Nachname: true,
|
||||
Geburtstag: true,
|
||||
Anstelldatum: true,
|
||||
}
|
||||
})
|
||||
|
||||
res.send(articles)
|
||||
})
|
||||
|
||||
app.delete('/api/article/:article', async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
|
||||
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const article = await prisma.articles.findUnique({
|
||||
where: {
|
||||
ID: req.params.article
|
||||
},
|
||||
select: {
|
||||
ID: true,
|
||||
title: true,
|
||||
views: true,
|
||||
|
||||
author: {
|
||||
select: {
|
||||
ID: true,
|
||||
username: true
|
||||
}
|
||||
},
|
||||
sponsors: true,
|
||||
updatedAt: true,
|
||||
createdAt: true,
|
||||
}
|
||||
})
|
||||
|
||||
if (!article) return res.status(404).send(Errors.NOT_FOUND)
|
||||
|
||||
if ((!user.admin && !user.article_manage) && article?.author.ID !== user.ID) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
await prisma.articles.delete({
|
||||
where: {
|
||||
ID: req.params.article
|
||||
}
|
||||
})
|
||||
|
||||
fs.rmSync(`/var/lib/rheinefuerrheine/artikel/${req.params.article}.html`)
|
||||
if (fs.existsSync(`/var/lib/rheinefuerrheine/artikel/${req.params.article}.banner`)) fs.rmSync(`/var/lib/rheinefuerrheine/artikel/${req.params.article}.banner`)
|
||||
|
||||
res.send('OK')
|
||||
})
|
||||
|
||||
// app.post('/api/article/banner/:article', async (req, res) => {
|
||||
// const token = req.headers.authorization?.split(' ')[1]
|
||||
|
||||
// if(!token || !req.body) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
// const user = await authorize(token)
|
||||
// if(!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
// fs.writeFileSync(`/var/lib/rheinefuerrheine/artikel/${req.params.article}.banner`, req.body.data);
|
||||
|
||||
// res.send('OK')
|
||||
// })
|
||||
|
||||
app.get('/api/article/banner/:article', async (req, res) => {
|
||||
if (!fs.existsSync(`/var/lib/rheinefuerrheine/artikel/${req.params.article}.banner`)) return res.status(404).send(Errors.NOT_FOUND)
|
||||
|
||||
const content = fs.readFileSync(`/var/lib/rheinefuerrheine/artikel/${req.params.article}.banner`)
|
||||
|
||||
// its a base64 encoded image. The type is appended at the beginning of the string (e.g. data:image/png;base64,....) Extract the type and send it as content type header and the rest as the body
|
||||
const type = content.toString().split(';')[0].split(':')[1]
|
||||
res.setHeader('Content-Type', type)
|
||||
res.send(Buffer.from(content.toString().split(';base64,')[1], 'base64'))
|
||||
})
|
||||
|
||||
|
||||
app.get('/api/sponsors/', async (req, res) => {
|
||||
const sponsors = await prisma.sponsors.findMany({
|
||||
orderBy: {
|
||||
addedAt: 'desc'
|
||||
}
|
||||
})
|
||||
|
||||
res.send(sponsors)
|
||||
})
|
||||
|
||||
app.post('/api/sponsors/create', async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
|
||||
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const { name, description, url, logo, banner } = req.body
|
||||
if (!name || !description || !url || !logo || !banner) return res.status(400).send(Errors.MISSING_ITEMS)
|
||||
|
||||
|
||||
const sponsor = await prisma.sponsors.create({
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
url
|
||||
}
|
||||
})
|
||||
|
||||
fs.writeFileSync(`/var/lib/rheinefuerrheine/sponsoren/${sponsor.ID}.logo`, logo)
|
||||
fs.writeFileSync(`/var/lib/rheinefuerrheine/sponsoren/${sponsor.ID}.banner`, banner)
|
||||
|
||||
res.send(sponsor)
|
||||
})
|
||||
|
||||
app.patch('/api/sponsors/edit/:id', async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
|
||||
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const { name, description, url, logo, banner } = req.body
|
||||
if (!name || !description || !url) return res.status(400).send(Errors.MISSING_ITEMS)
|
||||
|
||||
const sponsor = await prisma.sponsors.update({
|
||||
where: {
|
||||
ID: req.params.id
|
||||
},
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
url,
|
||||
}
|
||||
})
|
||||
|
||||
if (logo) fs.writeFileSync(`/var/lib/rheinefuerrheine/sponsoren/${sponsor.ID}.logo`, logo)
|
||||
if (banner) fs.writeFileSync(`/var/lib/rheinefuerrheine/sponsoren/${sponsor.ID}.banner`, banner)
|
||||
|
||||
res.send(sponsor)
|
||||
})
|
||||
|
||||
app.delete('/api/sponsors/delete/:id', async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
|
||||
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
await prisma.sponsors.delete({
|
||||
where: {
|
||||
ID: req.params.id
|
||||
}
|
||||
})
|
||||
|
||||
fs.rmSync(`/var/lib/rheinefuerrheine/sponsoren/${req.params.id}.logo`)
|
||||
fs.rmSync(`/var/lib/rheinefuerrheine/sponsoren/${req.params.id}.banner`)
|
||||
|
||||
res.send('OK')
|
||||
})
|
||||
|
||||
app.get('/api/sponsors/:id', async (req, res) => {
|
||||
const sponsor = await prisma.sponsors.findUnique({
|
||||
where: {
|
||||
ID: req.params.id
|
||||
}
|
||||
})
|
||||
|
||||
if (!sponsor) return res.status(404).send(Errors.NOT_FOUND)
|
||||
|
||||
res.send(sponsor)
|
||||
})
|
||||
|
||||
app.get('/api/sponsors/logo/:id', async (req, res) => {
|
||||
if (!fs.existsSync(`/var/lib/rheinefuerrheine/sponsoren/${req.params.id}.logo`)) return res.status(404).send(Errors.NOT_FOUND)
|
||||
|
||||
const content = fs.readFileSync(`/var/lib/rheinefuerrheine/sponsoren/${req.params.id}.logo`)
|
||||
|
||||
// its a base64 encoded image. The type is appended at the beginning of the string (e.g. data:image/png;base64,....) Extract the type and send it as content type header and the rest as the body
|
||||
const type = content.toString().split(';')[0].split(':')[1]
|
||||
res.setHeader('Content-Type', type)
|
||||
res.send(Buffer.from(content.toString().split(';base64,')[1], 'base64'))
|
||||
})
|
||||
|
||||
app.get('/api/sponsors/banner/:id', async (req, res) => {
|
||||
if (!fs.existsSync(`/var/lib/rheinefuerrheine/sponsoren/${req.params.id}.banner`)) return res.status(404).send(Errors.NOT_FOUND)
|
||||
|
||||
const content = fs.readFileSync(`/var/lib/rheinefuerrheine/sponsoren/${req.params.id}.banner`)
|
||||
|
||||
// its a base64 encoded image. The type is appended at the beginning of the string (e.g. data:image/png;base64,....) Extract the type and send it as content type header and the rest as the body
|
||||
const type = content.toString().split(';')[0].split(':')[1]
|
||||
res.setHeader('Content-Type', type)
|
||||
res.send(Buffer.from(content.toString().split(';base64,')[1], 'base64'))
|
||||
res.send(workers)
|
||||
})
|
||||
|
||||
app.get('/api/users', async (req, res) => {
|
||||
|
@ -430,17 +103,12 @@ app.get('/api/users', async (req, res) => {
|
|||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
if (!user.admin && !user.user_manage) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const users = await prisma.users.findMany({
|
||||
select: {
|
||||
ID: true,
|
||||
username: true,
|
||||
admin: true,
|
||||
article_create: true,
|
||||
article_manage: true,
|
||||
sponsor_manage: true,
|
||||
user_manage: true,
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -455,7 +123,6 @@ app.get('/api/user/:id', async (req, res) => {
|
|||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
if (!user.admin && !user.user_manage) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const foundUser = await prisma.users.findUnique({
|
||||
where: {
|
||||
|
@ -463,12 +130,7 @@ app.get('/api/user/:id', async (req, res) => {
|
|||
},
|
||||
select: {
|
||||
ID: true,
|
||||
username: true,
|
||||
admin: true,
|
||||
article_create: true,
|
||||
article_manage: true,
|
||||
sponsor_manage: true,
|
||||
user_manage: true,
|
||||
username: true
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -485,21 +147,15 @@ app.post('/api/users/create', async (req, res) => {
|
|||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
if (!user.admin && !user.user_manage) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const { username, password, admin, article_create, article_manage, sponsor_manage, user_manage } = req.body
|
||||
if (!username || !password || admin === undefined || article_create === undefined || article_manage === undefined || sponsor_manage === undefined || user_manage === undefined) return res.status(400).send(Errors.MISSING_ITEMS)
|
||||
const { username, password } = req.body
|
||||
if (!username || !password) return res.status(400).send(Errors.MISSING_ITEMS)
|
||||
|
||||
const newUser = await prisma.users.create({
|
||||
data: {
|
||||
username,
|
||||
password,
|
||||
token: crypto.randomBytes(64).toString('hex'),
|
||||
admin,
|
||||
article_create,
|
||||
article_manage,
|
||||
sponsor_manage,
|
||||
user_manage,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -514,7 +170,6 @@ app.patch('/api/users/edit/:id', async (req, res) => {
|
|||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
if (!user.admin && !user.user_manage) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const { username, password, admin, article_create, article_manage, sponsor_manage, user_manage } = req.body
|
||||
if (!username || admin === undefined || article_create === undefined || article_manage === undefined || sponsor_manage === undefined || user_manage === undefined) return res.status(400).send(Errors.MISSING_ITEMS)
|
||||
|
@ -544,9 +199,6 @@ app.delete('/api/users/delete/:id', async (req, res) => {
|
|||
|
||||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
if (!user.admin && !user.user_manage) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
await prisma.users.delete({
|
||||
where: {
|
||||
ID: req.params.id
|
||||
|
@ -556,31 +208,8 @@ app.delete('/api/users/delete/:id', async (req, res) => {
|
|||
res.send('OK')
|
||||
})
|
||||
|
||||
app.get('/api/stats', async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
if (!token) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const user = await authorize(token)
|
||||
if (!user) return res.status(401).send(Errors.INVALID_CREDENTIALS)
|
||||
|
||||
const articles = await prisma.articles.count()
|
||||
const users = await prisma.users.count()
|
||||
const sponsors = await prisma.sponsors.count()
|
||||
const views = await prisma.articles.aggregate({
|
||||
_sum: {
|
||||
views: true
|
||||
}
|
||||
})
|
||||
|
||||
res.send({
|
||||
articles,
|
||||
users,
|
||||
sponsors,
|
||||
views: views._sum.views
|
||||
})
|
||||
})
|
||||
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log(`Server is running on port ${process.env.PORT}`)
|
||||
})
|
||||
|
||||
})
|
|
@ -1,28 +1,30 @@
|
|||
import { prisma } from '.'
|
||||
import crypto from 'crypto'
|
||||
import Logger from './logger';
|
||||
import { prisma } from ".";
|
||||
import crypto from "crypto";
|
||||
import Logger from "./logger";
|
||||
|
||||
const logger = new Logger('Init');
|
||||
const logger = new Logger("Init");
|
||||
|
||||
export default async function init() {
|
||||
logger.info('Checking database...');
|
||||
const users = await prisma.users.count();
|
||||
|
||||
if (users === 0) {
|
||||
logger.warn('No users found, creating admin user...');
|
||||
await prisma.$transaction([
|
||||
prisma.users.create({
|
||||
data: {
|
||||
username: 'admin',
|
||||
password: crypto.createHash('sha256').update('rheine admin rheine').digest('hex'),
|
||||
token: crypto.randomBytes(64).toString('hex'),
|
||||
admin: true
|
||||
}
|
||||
})
|
||||
]);
|
||||
logger.info("Checking database...");
|
||||
const users = await prisma.users.count();
|
||||
|
||||
logger.info('Admin user created! Username: admin, Password: admin');
|
||||
}
|
||||
if (users === 0) {
|
||||
logger.warn("No users found, creating admin user...");
|
||||
await prisma.$transaction([
|
||||
prisma.users.create({
|
||||
data: {
|
||||
username: "admin",
|
||||
password: crypto
|
||||
.createHash("sha256")
|
||||
.update("rheine admin rheine")
|
||||
.digest("hex"),
|
||||
token: crypto.randomBytes(64).toString("hex"),
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
logger.info('Database is ready!');
|
||||
}
|
||||
logger.info("Admin user created! Username: admin, Password: admin");
|
||||
}
|
||||
|
||||
logger.info("Database is ready!");
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@mui/icons-material": "^5.14.9",
|
||||
"@mui/lab": "^5.0.0-alpha.154",
|
||||
"@mui/material": "^5.14.10",
|
||||
"@mui/x-data-grid": "^6.19.1",
|
||||
"@types/node": "^16.18.52",
|
||||
"@types/react": "^18.2.22",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
|
@ -22,6 +23,7 @@
|
|||
"js-sha256": "^0.10.1",
|
||||
"moment": "^2.29.4",
|
||||
"react": "^18.2.0",
|
||||
"react-calendar": "^4.8.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"react-router-dom": "^6.16.0",
|
||||
|
@ -3645,6 +3647,31 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "6.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.19.1.tgz",
|
||||
"integrity": "sha512-qtmt+XAOdxwb7p3vjk4HcfncAOe+0HzpcC6o6G+rzk9Hqq6MgG3yODrN9mRc/ONg0fD0eVlqDogXxNtJhcFmTQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"@mui/utils": "^5.14.16",
|
||||
"clsx": "^2.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^4.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^5.4.1",
|
||||
"@mui/system": "^5.4.1",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
||||
"version": "5.1.1-v1",
|
||||
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
||||
|
@ -4278,6 +4305,19 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.202",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
|
||||
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ=="
|
||||
},
|
||||
"node_modules/@types/lodash.memoize": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz",
|
||||
"integrity": "sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==",
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
|
@ -4782,6 +4822,14 @@
|
|||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@wojtekmaj/date-utils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz",
|
||||
"integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==",
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/date-utils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
|
@ -8641,6 +8689,18 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-user-locale": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.1.tgz",
|
||||
"integrity": "sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==",
|
||||
"dependencies": {
|
||||
"@types/lodash.memoize": "^4.1.7",
|
||||
"lodash.memoize": "^4.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
|
@ -14491,6 +14551,31 @@
|
|||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"node_modules/react-calendar": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-4.8.0.tgz",
|
||||
"integrity": "sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA==",
|
||||
"dependencies": {
|
||||
"@wojtekmaj/date-utils": "^1.1.3",
|
||||
"clsx": "^2.0.0",
|
||||
"get-user-locale": "^2.2.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/react-calendar?sponsor=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-dev-utils": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||
|
@ -14951,6 +15036,11 @@
|
|||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.6",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
|
||||
|
@ -16894,6 +16984,14 @@
|
|||
"makeerror": "1.0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"@mui/icons-material": "^5.14.9",
|
||||
"@mui/lab": "^5.0.0-alpha.154",
|
||||
"@mui/material": "^5.14.10",
|
||||
"@mui/x-data-grid": "^6.19.1",
|
||||
"@types/node": "^16.18.52",
|
||||
"@types/react": "^18.2.22",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
|
@ -17,6 +18,7 @@
|
|||
"js-sha256": "^0.10.1",
|
||||
"moment": "^2.29.4",
|
||||
"react": "^18.2.0",
|
||||
"react-calendar": "^4.8.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"react-router-dom": "^6.16.0",
|
||||
|
@ -28,7 +30,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"build": "react-scripts build && cp -a build/* ../backend/public",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Rheine Für Rheine</title>
|
||||
<title>DWL Birthdays</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 149 KiB |
|
@ -3,22 +3,16 @@ import { Routes, Route } from "react-router-dom";
|
|||
// @Pages
|
||||
import LandingPage from "./pages/LandingPage";
|
||||
import LoginPage from "./pages/Login";
|
||||
import Datenschutz from "./pages/Datenschutz";
|
||||
import Impressum from "./pages/Impressum";
|
||||
import NotFound from "./pages/404";
|
||||
import AdminFrame from "./pages/AdminFrame";
|
||||
import Artikel from "./pages/Artikel";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<LandingPage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/datenschutz" element={<Datenschutz />} />
|
||||
<Route path="/impressum" element={<Impressum />} />
|
||||
<Route path="/admin/*" element={<AdminFrame />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
<Route path="/artikel/:id" element={<Artikel />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,201 +0,0 @@
|
|||
import { Grid, Typography } from "@mui/material";
|
||||
|
||||
export function AboutSection(): JSX.Element {
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
|
||||
"@media (max-width: 1200px)": {
|
||||
pt: "50px",
|
||||
},
|
||||
}}
|
||||
id="about"
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
lg={6}
|
||||
md={12}
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-start",
|
||||
gap: "10px",
|
||||
|
||||
px: "5%",
|
||||
}}
|
||||
>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bolder",
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "4rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "left",
|
||||
color: "primary.main",
|
||||
}}
|
||||
fontFamily="Lexend Variable"
|
||||
>
|
||||
Über uns
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "light",
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "1.4rem",
|
||||
textAlign: "left",
|
||||
}}
|
||||
fontFamily="Overpass Variable"
|
||||
>
|
||||
Gemeinsam stark für Rheine – für die Rheinenser sein. Aus diesem Grund
|
||||
unterstützen wir die: Straßenparty – Rheines Stadtfest.
|
||||
<br /> <br />
|
||||
Wir möchten etwas bewegen, ein Zeichen setzen – wir möchten, dass die
|
||||
Rheinenser und die Besucher der Straßenparty ein tolles Programm und
|
||||
schöne musikalische Highlights genießen dürfen. Dafür haben wir uns
|
||||
zusammengetan.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6} sx={{
|
||||
"@media (max-width: 1200px)": {
|
||||
display: "none",
|
||||
},
|
||||
}}>
|
||||
<Grid
|
||||
container
|
||||
gap={1}
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "5%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={5.5}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
|
||||
overflow: "hidden",
|
||||
|
||||
filter: "brightness(0.8)",
|
||||
"&:hover > :first-child": {
|
||||
transform: "scale(1.1)",
|
||||
filter: "brightness(1)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/assets/background1.jpg"
|
||||
alt="about-1"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
objectFit: "contain",
|
||||
transition: "all 0.5s ease",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={5.5}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "primary.main",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
color: "#fff",
|
||||
fontWeight: "light",
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "4rem",
|
||||
textAlign: "center",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
Unser <strong style={{
|
||||
fontWeight: "bold",
|
||||
marginTop: "-20px",
|
||||
}}>Rheine</strong>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={5.5}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "primary.main"
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
color: "#fff",
|
||||
fontWeight: "light",
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "4rem",
|
||||
textAlign: "center",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
Euer <strong style={{
|
||||
fontWeight: "bold",
|
||||
marginTop: "-20px",
|
||||
}}>Rheine</strong>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={5.5}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
|
||||
overflow: "hidden",
|
||||
|
||||
filter: "brightness(0.8)",
|
||||
"&:hover > :first-child": {
|
||||
transform: "scale(1.1)",
|
||||
filter: "brightness(1)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/assets/background2.jpg"
|
||||
alt="about-1"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
objectFit: "contain",
|
||||
transition: "all 0.5s ease",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
|
@ -1,344 +0,0 @@
|
|||
import { Box, Grid, Typography } from "@mui/material";
|
||||
import { Description as DescriptionIcon, Newspaper, Public } from "@mui/icons-material";
|
||||
|
||||
export function AboutSection2(): JSX.Element {
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
id="info"
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
|
||||
py: "40px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={6}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "0px",
|
||||
|
||||
"@media (max-width: 1200px)": {
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "10%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "auto",
|
||||
height: "70px",
|
||||
|
||||
px: "30px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
|
||||
backgroundColor: "primary.main",
|
||||
}}
|
||||
>
|
||||
<DescriptionIcon
|
||||
fontSize="large"
|
||||
sx={{
|
||||
color: "#fff",
|
||||
}} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "26px",
|
||||
fontWeight: "800",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
32.000
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "12px",
|
||||
fontWeight: "light",
|
||||
color: "#fff",
|
||||
mt: "-5px",
|
||||
}}
|
||||
>
|
||||
Flyer der Straßenparty
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "auto",
|
||||
height: "70px",
|
||||
|
||||
px: "30px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
|
||||
backgroundColor: "primary.main",
|
||||
}}
|
||||
>
|
||||
<Public
|
||||
fontSize="large"
|
||||
sx={{
|
||||
color: "#fff",
|
||||
}} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "26px",
|
||||
fontWeight: "800",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
www
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "12px",
|
||||
fontWeight: "light",
|
||||
color: "#fff",
|
||||
mt: "-5px",
|
||||
}}
|
||||
>
|
||||
vonrheinefuerrheine.de
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "40px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "auto",
|
||||
height: "70px",
|
||||
|
||||
px: "30px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
|
||||
backgroundColor: "primary.main",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "26px",
|
||||
fontWeight: "800",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
Hauptbühne
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "12px",
|
||||
fontWeight: "light",
|
||||
color: "#fff",
|
||||
mt: "-5px",
|
||||
}}
|
||||
>
|
||||
Eröffnung & Logodarstellung
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "40px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "auto",
|
||||
height: "70px",
|
||||
|
||||
px: "30px",
|
||||
ml: "20%",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
|
||||
backgroundColor: "primary.main",
|
||||
}}
|
||||
>
|
||||
<Newspaper
|
||||
fontSize="large"
|
||||
sx={{
|
||||
color: "#fff",
|
||||
}} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "26px",
|
||||
fontWeight: "800",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
Presse
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "12px",
|
||||
fontWeight: "light",
|
||||
color: "#fff",
|
||||
mt: "-5px",
|
||||
}}
|
||||
>
|
||||
Verschiedene Berichte
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
md={12}
|
||||
xl={6}
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-end",
|
||||
|
||||
px: "5%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bolder",
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "4rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "right",
|
||||
color: "primary.main",
|
||||
}}
|
||||
fontFamily="Lexend Variable"
|
||||
>
|
||||
Mehrwert
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "3rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "right",
|
||||
color: "primary.main",
|
||||
|
||||
mb: "40px",
|
||||
}}
|
||||
fontFamily="Lexend Variable"
|
||||
>
|
||||
für alle Unterstützer
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "light",
|
||||
fontFamily: "Overpass Variable",
|
||||
fontSize: "1.4rem",
|
||||
textAlign: "right",
|
||||
}}
|
||||
fontFamily="Overpass Variable"
|
||||
>
|
||||
Die Pandemie hat vielen Förderern der Stadt Rheine die Möglichkeit
|
||||
genommen zu unterstützen und wir, als regionale Dienstleister und
|
||||
Unternehmen, möchten wieder nach vorne schauen und Veranstaltungen für
|
||||
die Bürger für unsere Nachbarn, Kunden, Freunde und Bekannte
|
||||
ermöglichen. Teilnehmer können sich über folgendes freuen:
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
import { Avatar, Box, Button, Typography } from "@mui/material";
|
||||
import { getBaseURL } from "../functions";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
function ArticleCard({ id, name }: { id: string; name: string }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "500px",
|
||||
height: "300px",
|
||||
|
||||
backgroundColor: "#FFF",
|
||||
|
||||
borderRadius: "13px",
|
||||
boxShadow: (theme) => `2px 5px 7px ${theme.palette.primary.light}`,
|
||||
|
||||
// clip the corners
|
||||
overflow: "hidden",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s ease-in-out",
|
||||
|
||||
"&:hover": {
|
||||
boxShadow: (theme) => `2px 5px 7px ${theme.palette.primary.main}`,
|
||||
transform: "scale(1.02)",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/artikel/${id}`);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={`${getBaseURL()}/api/article/banner/${id}`}
|
||||
alt=""
|
||||
style={{
|
||||
width: "500px",
|
||||
height: "150px",
|
||||
|
||||
objectFit: "cover",
|
||||
minWidth: "500px",
|
||||
minHeight: "150px",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
mt: "0px",
|
||||
|
||||
width: "100%",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
margin="none"
|
||||
sx={{
|
||||
ml: "10px",
|
||||
width: "100%",
|
||||
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "38px",
|
||||
fontWeight: 800,
|
||||
fontStyle: "italic",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArticleCard;
|
|
@ -1,209 +0,0 @@
|
|||
import { EngineeringOutlined } from "@mui/icons-material";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { FooterLink } from "./FooterLink";
|
||||
|
||||
export function Footer(): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "250px",
|
||||
|
||||
mt: "50px",
|
||||
px: "20%",
|
||||
py: "50px",
|
||||
|
||||
backgroundColor: "primary.main",
|
||||
color: "#fff",
|
||||
|
||||
"@media (max-width: 1200px)": {
|
||||
px: "40px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
|
||||
gap: "3px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#fff",
|
||||
fontWeight: "bold",
|
||||
fontSize: "1.5rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "left",
|
||||
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
Links
|
||||
</Typography>
|
||||
<Link
|
||||
to="/#about"
|
||||
onClick={() => {
|
||||
const element = document.getElementById("about");
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
className="nav-link-left"
|
||||
sx={{
|
||||
color: "#fff",
|
||||
fontWeight: "light",
|
||||
fontSize: "1.25rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
Über uns
|
||||
</Typography>
|
||||
</Link>
|
||||
<Link
|
||||
to="/#info"
|
||||
onClick={() => {
|
||||
const element = document.getElementById("info");
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
className="nav-link-left"
|
||||
sx={{
|
||||
color: "#fff",
|
||||
fontWeight: "light",
|
||||
fontSize: "1.25rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
Für Sie
|
||||
</Typography>
|
||||
</Link>
|
||||
<Link
|
||||
to="/#sponsors"
|
||||
onClick={() => {
|
||||
const element = document.getElementById("sponsors");
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
className="nav-link-left"
|
||||
sx={{
|
||||
color: "#fff",
|
||||
fontWeight: "light",
|
||||
fontSize: "1.25rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
Sponsoren
|
||||
</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
|
||||
gap: "3px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#fff",
|
||||
fontWeight: "bold",
|
||||
fontSize: "1.5rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "left",
|
||||
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
Rechtliches
|
||||
</Typography>
|
||||
<FooterLink to="/datenschutz">Datenschutz</FooterLink>
|
||||
<FooterLink to="/impressum">Impressum</FooterLink>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<a
|
||||
style={{
|
||||
color: "#fff",
|
||||
fontWeight: "light",
|
||||
fontSize: "1rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "center",
|
||||
}}
|
||||
href="https://ipmake.dev"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
© 2023 <br /> IPGSystems inc. All rights reserved
|
||||
</a>
|
||||
<Button
|
||||
onClick={() => navigate("/login")}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<EngineeringOutlined />}
|
||||
sx={{
|
||||
ml: "auto",
|
||||
position: "absolute",
|
||||
right: "20px",
|
||||
color: "#fff",
|
||||
"&:hover": {
|
||||
transform: "scale(1.1)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Admin
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import { Typography } from "@mui/material";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function FooterLink({
|
||||
to, children,
|
||||
}: {
|
||||
to: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <Link to={to}>
|
||||
<Typography
|
||||
className="nav-link-left"
|
||||
sx={{
|
||||
color: "#fff",
|
||||
fontWeight: "light",
|
||||
fontSize: "1.25rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
</Link>;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Box, Divider } from "@mui/material"
|
||||
import SidebarElement from "./SidebarElement"
|
||||
import { ArrowBack, Home, Logout, Newspaper, Person, Savings } from "@mui/icons-material"
|
||||
import { ArrowBack, Home, Logout, Newspaper, Person, TableChartOutlined, Event } from "@mui/icons-material"
|
||||
|
||||
function Sidebar() {
|
||||
|
||||
|
@ -12,7 +12,7 @@ function Sidebar() {
|
|||
top: "0",
|
||||
left: "0",
|
||||
|
||||
backgroundColor: "#fff",
|
||||
backgroundColor: "#121212",
|
||||
boxShadow: "0 0 10px #00000055",
|
||||
|
||||
zIndex: 1000,
|
||||
|
@ -47,8 +47,8 @@ function Sidebar() {
|
|||
gap: "2px",
|
||||
}}>
|
||||
<SidebarElement Title="Dashboard" Icon={Home} Path="/admin/dashboard" />
|
||||
<SidebarElement Title="Artikel" Icon={Newspaper} Path="/admin/artikel" />
|
||||
<SidebarElement Title="Sponsoren" Icon={Savings} Path="/admin/sponsoren" />
|
||||
<SidebarElement Title="Daten import" Icon={TableChartOutlined} Path="/admin/import" />
|
||||
<SidebarElement Title="Kalender" Icon={Event} Path="/admin/Kalender" />
|
||||
<SidebarElement Title="Benutzer" Icon={Person} Path="/admin/benutzer" />
|
||||
|
||||
<SidebarElement Title="Zur Website" Icon={ArrowBack} Path="/" sx={{
|
||||
|
|
|
@ -47,7 +47,7 @@ function SidebarElement({
|
|||
<Icon
|
||||
sx={{
|
||||
fontSize: "30px",
|
||||
color: "#000000CC",
|
||||
color: "#fff",
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
|
@ -55,7 +55,7 @@ function SidebarElement({
|
|||
fontFamily: "Lexend Variable",
|
||||
fontSize: "20px",
|
||||
fontWeight: "bold",
|
||||
color: "#000000CC",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
{Title}
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
import { Avatar, Box, Button, Typography } from "@mui/material";
|
||||
import { getBaseURL } from "../functions";
|
||||
|
||||
function SponsorCard({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
url,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
}) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "500px",
|
||||
height: "300px",
|
||||
|
||||
backgroundColor: "#FFF",
|
||||
|
||||
borderRadius: "13px",
|
||||
boxShadow: theme => `2px 5px 7px ${theme.palette.primary.light}`,
|
||||
|
||||
// clip the corners
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={`${getBaseURL()}/api/sponsors/banner/${id}`}
|
||||
alt=""
|
||||
style={{
|
||||
width: "500px",
|
||||
height: "150px",
|
||||
|
||||
objectFit: "cover",
|
||||
minWidth: "500px",
|
||||
minHeight: "150px",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
mt: "-75px",
|
||||
|
||||
width: "500px",
|
||||
height: "225px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}>
|
||||
<Avatar
|
||||
variant="square"
|
||||
src={`${getBaseURL()}/api/sponsors/logo/${id}`}
|
||||
sx={{
|
||||
width: "150px",
|
||||
height: "150px",
|
||||
|
||||
borderRadius: "20px",
|
||||
ml: "25px",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
width: "150px",
|
||||
height: "50px",
|
||||
|
||||
mt: "10px",
|
||||
ml: "25px",
|
||||
|
||||
color: "#fff",
|
||||
|
||||
borderRadius: "20px",
|
||||
padding: "5px",
|
||||
"& .MuiButton-label": {
|
||||
p: "0px",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
window.open(url, "_blank");
|
||||
}}
|
||||
>
|
||||
Webseite
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "300px",
|
||||
height: "175px",
|
||||
background: "#fff",
|
||||
|
||||
mt: "35px",
|
||||
ml: "25px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
|
||||
borderTopLeftRadius: "13px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
margin="none"
|
||||
sx={{
|
||||
ml: "10px",
|
||||
width: "280px",
|
||||
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "38px",
|
||||
fontWeight: 800,
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography
|
||||
margin="none"
|
||||
sx={{
|
||||
height: "90px",
|
||||
minHeight: "90px",
|
||||
width: "280px",
|
||||
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "16px",
|
||||
fontWeight: 200,
|
||||
fontStyle: "italic",
|
||||
color: "#828282",
|
||||
ml: "10px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "top",
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default SponsorCard;
|
|
@ -1,270 +0,0 @@
|
|||
import { Avatar, Box, Button, TextField } from "@mui/material";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { getBaseURL } from "../functions";
|
||||
|
||||
function SponsorCardEditable({
|
||||
onSave,
|
||||
edit,
|
||||
}: {
|
||||
onSave: (result: {
|
||||
name: string,
|
||||
description: string,
|
||||
logo: string | null,
|
||||
banner: string | null,
|
||||
url: string,
|
||||
}) => void;
|
||||
edit?: {
|
||||
id: string,
|
||||
name: string,
|
||||
description: string,
|
||||
url: string,
|
||||
};
|
||||
}) {
|
||||
const logoRef = useRef<HTMLInputElement>(null);
|
||||
const bannerRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [logo, setLogo] = useState<string>("https://placehold.co/150x150");
|
||||
const [banner, setBanner] = useState<string>("https://placehold.co/500x150");
|
||||
|
||||
const [name, setName] = useState<string>("");
|
||||
const [description, setDescription] = useState<string>("");
|
||||
|
||||
const [url, setUrl] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!edit) return;
|
||||
|
||||
setName(edit.name);
|
||||
setDescription(edit.description);
|
||||
setUrl(edit.url);
|
||||
setLogo(`${getBaseURL()}/api/sponsors/logo/${edit.id}`);
|
||||
setBanner(`${getBaseURL()}/api/sponsors/banner/${edit.id}`);
|
||||
}, [edit]);
|
||||
|
||||
const valid = () => {
|
||||
if (!name) return false;
|
||||
if (!description) return false;
|
||||
if (!logo) return false;
|
||||
if (!banner) return false;
|
||||
if (!url) return false;
|
||||
|
||||
if (logo.startsWith("https://placehold.co/")) return false;
|
||||
if (banner.startsWith("https://placehold.co/")) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="file"
|
||||
ref={logoRef}
|
||||
accept="image/*"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
setLogo(reader.result as string);
|
||||
};
|
||||
}}
|
||||
onAbort={() => {}}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
ref={bannerRef}
|
||||
accept="image/*"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
setBanner(reader.result as string);
|
||||
};
|
||||
}}
|
||||
onAbort={() => {}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "500px",
|
||||
height: "300px",
|
||||
|
||||
backgroundColor: "#FFF",
|
||||
|
||||
borderRadius: "13px",
|
||||
border: "1px solid #00000033",
|
||||
|
||||
// clip the corners
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={banner}
|
||||
alt="banner"
|
||||
style={{
|
||||
width: "500px",
|
||||
height: "150px",
|
||||
|
||||
objectFit: "cover",
|
||||
minWidth: "500px",
|
||||
minHeight: "150px",
|
||||
|
||||
cursor: "crosshair",
|
||||
}}
|
||||
onClick={() => bannerRef.current?.click()}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
mt: "-75px",
|
||||
|
||||
width: "500px",
|
||||
height: "225px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
variant="square"
|
||||
src={logo}
|
||||
sx={{
|
||||
width: "150px",
|
||||
height: "150px",
|
||||
|
||||
borderRadius: "20px",
|
||||
ml: "25px",
|
||||
|
||||
cursor: "crosshair",
|
||||
}}
|
||||
onClick={() => {
|
||||
logoRef.current?.click();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "300px",
|
||||
height: "175px",
|
||||
background: "#fff",
|
||||
|
||||
mt: "35px",
|
||||
ml: "25px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
|
||||
borderTopLeftRadius: "13px",
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
multiline
|
||||
margin="none"
|
||||
variant="standard"
|
||||
fullWidth
|
||||
value={name}
|
||||
placeholder="Name"
|
||||
sx={{
|
||||
ml: "10px",
|
||||
width: "280px",
|
||||
|
||||
"& .MuiInputBase-root": {
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "38px",
|
||||
fontWeight: 800,
|
||||
fontStyle: "italic",
|
||||
},
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (e.target.value.length >= 12) return;
|
||||
if (e.target.value.includes("\n")) return;
|
||||
setName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
multiline
|
||||
margin="none"
|
||||
variant="standard"
|
||||
fullWidth
|
||||
value={description}
|
||||
placeholder="Beschreibung"
|
||||
sx={{
|
||||
height: "90px",
|
||||
minHeight: "90px",
|
||||
width: "280px",
|
||||
|
||||
"& .MuiInputBase-root": {
|
||||
display: "inline",
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "16px",
|
||||
fontWeight: 200,
|
||||
fontStyle: "italic",
|
||||
color: "#828282",
|
||||
ml: "10px",
|
||||
height: "90px",
|
||||
minHeight: "90px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "top",
|
||||
},
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (e.target.value.length >= 100) return;
|
||||
if (e.target.value.split("\n").length > 3) return;
|
||||
setDescription(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
margin="none"
|
||||
variant="standard"
|
||||
fullWidth
|
||||
value={url}
|
||||
placeholder="URL"
|
||||
sx={{
|
||||
mt: "20px",
|
||||
width: "80%",
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (e.target.value.length >= 128) return;
|
||||
if (e.target.value.includes("\n")) return;
|
||||
setUrl(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ width: "80%" }}
|
||||
disabled={!valid()}
|
||||
onClick={() => {
|
||||
onSave({
|
||||
name,
|
||||
description,
|
||||
logo,
|
||||
banner,
|
||||
url,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SponsorCardEditable;
|
|
@ -1,53 +0,0 @@
|
|||
import { Box, Grid, Avatar } from "@mui/material";
|
||||
|
||||
export function SponsorImageSmall({
|
||||
name,
|
||||
image,
|
||||
description,
|
||||
link,
|
||||
onRemove,
|
||||
}: {
|
||||
name: string;
|
||||
image: string;
|
||||
description: string;
|
||||
link: string;
|
||||
onRemove?: () => void;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<Grid item>
|
||||
<Box
|
||||
sx={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={image}
|
||||
alt=""
|
||||
sx={{
|
||||
height: "75px",
|
||||
width: "75px",
|
||||
borderRadius: "20px",
|
||||
|
||||
...(onRemove && {
|
||||
cursor: "crosshair",
|
||||
|
||||
"&:hover": {
|
||||
filter: "brightness(0.5)",
|
||||
},
|
||||
}),
|
||||
...(!onRemove && {
|
||||
cursor: "pointer",
|
||||
}),
|
||||
}}
|
||||
onClick={
|
||||
onRemove ||
|
||||
(() => {
|
||||
window.open(link, "_blank");
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
);
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
import { Close } from "@mui/icons-material";
|
||||
import {
|
||||
Backdrop,
|
||||
Box,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import SponsorCardEditable from "./SponsorCardEditable";
|
||||
import axios from "axios";
|
||||
import { getBaseURL } from "../functions";
|
||||
|
||||
function SponsorModal({
|
||||
id,
|
||||
onClose,
|
||||
}: {
|
||||
id: string | null;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [data, setData] = useState<Types.Sponsor | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setData(null);
|
||||
if(!id) return;
|
||||
if (id === "create") return;
|
||||
setLoading(true);
|
||||
|
||||
axios
|
||||
.get(`${getBaseURL()}/api/sponsors/${id}`)
|
||||
.then((res) => {
|
||||
setData(res.data);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
onClose?.();
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
if (!id) return <></>;
|
||||
if (loading)
|
||||
return (
|
||||
<Backdrop
|
||||
open={true}
|
||||
sx={{
|
||||
zIndex: 2000,
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "20px",
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
Lade...
|
||||
</Typography>
|
||||
</Backdrop>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{saving && (
|
||||
<Backdrop
|
||||
open={true}
|
||||
sx={{
|
||||
zIndex: 2000,
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "20px",
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
Speichere...
|
||||
</Typography>
|
||||
</Backdrop>
|
||||
)}
|
||||
<Backdrop
|
||||
open={true}
|
||||
sx={{
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "600px",
|
||||
height: "auto",
|
||||
padding: "10px",
|
||||
|
||||
backgroundColor: "#fff",
|
||||
boxShadow: "0px 0px 10px #00000033",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "40px",
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "28px",
|
||||
fontWeight: 700,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Sponsor { id === "create" ? "Erstellen" : "Bearbeiten" }
|
||||
</Typography>
|
||||
<IconButton onClick={onClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<SponsorCardEditable
|
||||
edit={(id && id !== "create" && {
|
||||
id: id,
|
||||
name: data?.name as string,
|
||||
description: data?.description as string,
|
||||
url: data?.url as string,
|
||||
}) || undefined}
|
||||
onSave={async (result) => {
|
||||
setSaving(true);
|
||||
if (id === "create") {
|
||||
axios
|
||||
.post(
|
||||
`${getBaseURL()}/api/sponsors/create`,
|
||||
{
|
||||
name: result.name,
|
||||
description: result.description,
|
||||
url: result.url,
|
||||
logo: result.logo,
|
||||
banner: result.banner,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem(
|
||||
"token"
|
||||
)}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
} else {
|
||||
result.logo = result.logo?.includes("http")
|
||||
? null
|
||||
: result.logo;
|
||||
result.banner = result.banner?.includes("http")
|
||||
? null
|
||||
: result.banner;
|
||||
|
||||
axios
|
||||
.patch(
|
||||
`${getBaseURL()}/api/sponsors/edit/${id}`,
|
||||
{
|
||||
name: result.name,
|
||||
description: result.description,
|
||||
url: result.url,
|
||||
logo: result.logo,
|
||||
banner: result.banner,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem(
|
||||
"token"
|
||||
)}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Backdrop>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SponsorModal;
|
|
@ -1,170 +0,0 @@
|
|||
import { AppBar, Box, Button, Drawer, useTheme } from "@mui/material";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Menu, DescriptionOutlined } from "@mui/icons-material";
|
||||
import { CSSProperties, useState } from "react";
|
||||
|
||||
// @Component
|
||||
import NavLink from "./NavLink";
|
||||
|
||||
|
||||
const MobileLinkStyle: CSSProperties = {
|
||||
height: "60px",
|
||||
width: '100%',
|
||||
|
||||
borderBottom: "1px solid #000",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}
|
||||
|
||||
function TopBar() {
|
||||
const theme = useTheme();
|
||||
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBar
|
||||
position="static"
|
||||
sx={{
|
||||
height: "100px",
|
||||
backgroundColor: "#ffffff",
|
||||
color: "#ffffff",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
|
||||
px: "15%",
|
||||
|
||||
boxShadow: "3px 2px 2px rgba(0, 0, 0, 0.25)",
|
||||
|
||||
"@media screen and (max-width: 768px)": {
|
||||
px: "0px",
|
||||
},
|
||||
|
||||
"@media screen and (max-width: 1300px)": {
|
||||
px: "2px",
|
||||
},
|
||||
|
||||
"@media screen and (max-width: 1800px)": {
|
||||
px: "10%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Link to="/">
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="logo"
|
||||
style={{
|
||||
width: "227px",
|
||||
height: "70px",
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 0,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: "50px",
|
||||
|
||||
"@media screen and (max-width: 768px)": {
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<NavLink to="/#sponsors">Sponsoren</NavLink>
|
||||
<NavLink to="/#about">Über uns</NavLink>
|
||||
<NavLink to="/#info">Für Sie</NavLink>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<DescriptionOutlined />}
|
||||
sx={{
|
||||
color: "#ffffff",
|
||||
|
||||
transition: "all 0.3s ease-in-out",
|
||||
|
||||
borderRadius: "none",
|
||||
|
||||
"&:hover": {
|
||||
transform: "scale(1.1)",
|
||||
},
|
||||
|
||||
"@media screen and (max-width: 1000px)": {
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
href="https://www.rheine-tourismus.de/media/www.rheine-tourismus.de/org/med_1760/10298_strassenparty-flyer.n.pdf"
|
||||
>
|
||||
Download Flyer
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
backdropFilter: "none",
|
||||
boxShadow: "none",
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
boxShadow: `3px 3px 3px 0px ${theme.palette.secondary.main}`,
|
||||
},
|
||||
|
||||
"@media screen and (min-width: 768px)": {
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
>
|
||||
<Menu />
|
||||
</Button>
|
||||
</AppBar>
|
||||
<Drawer
|
||||
anchor="right"
|
||||
open={sidebarOpen}
|
||||
onClose={() => setSidebarOpen(false)}
|
||||
sx={{
|
||||
"@media screen and (min-width: 768px)": {
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
width: 250,
|
||||
overflow: 'auto',
|
||||
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
gap: '0px',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
onClick: () => setSidebarOpen(false),
|
||||
}}
|
||||
>
|
||||
<NavLink to="#about" style={MobileLinkStyle}>Über uns</NavLink>
|
||||
<NavLink to="#info" style={MobileLinkStyle}>Für Sie</NavLink>
|
||||
<NavLink to="#sponsors" style={MobileLinkStyle}>Sponsoren</NavLink>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopBar;
|
|
@ -1,5 +1,5 @@
|
|||
export function getBaseURL(): string {
|
||||
if(process.env.NODE_ENV === "development") return "http://localhost:3001"
|
||||
if(process.env.NODE_ENV === "development") return "http://100.108.94.138:3001"
|
||||
else return ""
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@ import '@fontsource-variable/lexend';
|
|||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: "dark",
|
||||
primary: {
|
||||
main: "#6f9c01",
|
||||
main: "#003263",
|
||||
},
|
||||
secondary: {
|
||||
main: "#00ACD3",
|
||||
main: "#9bc9e5",
|
||||
},
|
||||
},
|
||||
// make the buttons not rounded
|
||||
|
|
|
@ -2,11 +2,10 @@ import { Box } from "@mui/material";
|
|||
import { Route, Routes } from "react-router-dom";
|
||||
import Sidebar from "../components/Sidebar";
|
||||
import Dashboard from "./admin/Dashboard";
|
||||
import Artikel from "./admin/Artikel";
|
||||
import Sponsoren from "./admin/Sponsoren";
|
||||
import Benutzer from "./admin/Benutzer";
|
||||
import ArticleEditor from "./admin/ArticleEditor";
|
||||
import Logout from "./admin/Logout";
|
||||
import Import from "./admin/Import";
|
||||
import Kalender from "./admin/Kalender";
|
||||
|
||||
function AdminFrame() {
|
||||
return (
|
||||
|
@ -30,12 +29,10 @@ function AdminFrame() {
|
|||
}}>
|
||||
<Routes>
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/artikel" element={<Artikel />} />
|
||||
<Route path="/sponsoren" element={<Sponsoren />} />
|
||||
<Route path="/benutzer" element={<Benutzer />} />
|
||||
<Route path="/editor" element={<ArticleEditor />} />
|
||||
<Route path="/editor/:id" element={<ArticleEditor />} />
|
||||
<Route path="/logout" element={<Logout />} />
|
||||
<Route path="/import" element={<Import />} />
|
||||
<Route path="/kalender" element={<Kalender />} />
|
||||
</Routes>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
object table {
|
||||
border: 1px solid #e1e1e1;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
object table td {
|
||||
border: 1px solid #e1e1e1;
|
||||
}
|
||||
|
||||
object figure {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
object pre {
|
||||
background: #f9f9f9;
|
||||
padding: 8px;
|
||||
color: #666;
|
||||
line-height: 1.45;
|
||||
border: 1px solid #e1e1e1;
|
||||
border-radius: 2px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
object {
|
||||
font-family: 'Lexend Variable';
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.se-toolbar-sticky-dummy {
|
||||
height: 0 !important;
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
/* eslint-disable jsx-a11y/alt-text */
|
||||
import { useParams } from "react-router-dom";
|
||||
import TopBar from "../components/TopBar";
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
Grid,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { getBaseURL } from "../functions";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
import "./Artikel.css";
|
||||
import "suneditor/dist/css/suneditor.min.css";
|
||||
import { SponsorImageSmall } from "../components/SponsorImageSmall";
|
||||
|
||||
function Artikel() {
|
||||
const ref = useRef<HTMLObjectElement>(null);
|
||||
const { id } = useParams() as {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const [loadingContent, setLoadingContent] = useState(true);
|
||||
const [article, setArticle] = useState<Types.Article | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
|
||||
axios.get(`${getBaseURL()}/api/article/content/${id}`).then((response) => {
|
||||
if (ref.current) {
|
||||
ref.current.innerHTML = response.data;
|
||||
setLoadingContent(false);
|
||||
}
|
||||
});
|
||||
|
||||
axios.get(`${getBaseURL()}/api/article/view/${id}`).then((response) => {
|
||||
setArticle(response.data);
|
||||
setTimeout(() => {
|
||||
// get all images with a data-size property
|
||||
const images = document.querySelectorAll("img[data-size]");
|
||||
console.log(images);
|
||||
|
||||
// loop through all images and set the width style to the data-size property
|
||||
for (let image of [...images]) {
|
||||
(image as HTMLElement).style.width =
|
||||
image.getAttribute("data-size")?.replaceAll(",", "") ?? "auto";
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
{loadingContent && <CircularProgress />}
|
||||
{!loadingContent && (
|
||||
<Box
|
||||
sx={{
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: -1,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={`${getBaseURL()}/api/article/banner/${id}`}
|
||||
alt=""
|
||||
style={{
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
zIndex: -1,
|
||||
filter: "brightness(0.5)",
|
||||
objectFit: "cover",
|
||||
minWidth: "100vw",
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
|
||||
px: "15%",
|
||||
|
||||
"@media screen and (max-width: 768px)": {
|
||||
px: "0px",
|
||||
},
|
||||
|
||||
"@media screen and (max-width: 1300px)": {
|
||||
px: "2px",
|
||||
},
|
||||
|
||||
"@media screen and (min-width: 1800px)": {
|
||||
px: "15%",
|
||||
},
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
|
||||
transform: "translateY(25vh)",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "1080px",
|
||||
height: "fit-content",
|
||||
boxShadow: "0px 0px 10px 0px rgba(0,0,0,0.5)",
|
||||
opacity: loadingContent ? 0 : 1,
|
||||
padding: "40px",
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h1"
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "3rem",
|
||||
fontWeight: 700,
|
||||
textAlign: "left",
|
||||
mb: "20px",
|
||||
}}
|
||||
>
|
||||
{article?.title}
|
||||
</Typography>
|
||||
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
sx={{
|
||||
pb: "20px",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
{article?.sponsors.map((sponsor) => (
|
||||
<Tooltip title={sponsor.name}>
|
||||
<SponsorImageSmall
|
||||
name={sponsor.name}
|
||||
image={`${getBaseURL()}/api/sponsors/logo/${sponsor.ID}`}
|
||||
description={sponsor.description}
|
||||
link={sponsor.url}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<object
|
||||
id="article-content"
|
||||
height="fit-content"
|
||||
type="text/html"
|
||||
ref={ref}
|
||||
style={{
|
||||
width: "1000px",
|
||||
height: "fit-content",
|
||||
border: "none",
|
||||
padding: "0px",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Artikel;
|
|
@ -1,317 +0,0 @@
|
|||
import { Box, Divider, SxProps, Theme, Typography } from "@mui/material";
|
||||
import TopBar from "../components/TopBar";
|
||||
import { Footer } from "../components/Footer";
|
||||
import { useEffect } from "react";
|
||||
|
||||
let dividerStyle: SxProps<Theme> = {
|
||||
my: "20px",
|
||||
width: "100%",
|
||||
background: (theme) => theme.palette.secondary.main,
|
||||
};
|
||||
|
||||
function Datenschutz() {
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
px: "10vw",
|
||||
mt: "5vh",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "800",
|
||||
mb: "5vh",
|
||||
fontSize: "3rem",
|
||||
}}
|
||||
>
|
||||
Datenschutzerklärung
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "light",
|
||||
mb: "5vh",
|
||||
fontSize: "1rem",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<strong>Verantwortlicher</strong>
|
||||
<br />
|
||||
Verantwortlicher für diese Internetseiten ist RF Computer GmbH & Co.
|
||||
KG., Otto-Bergmeyer-Str. 7, 48431 Rheine. Weitere Angaben zu unserem
|
||||
Unternehmen und den vertretungsberechtigten Personen können Sie
|
||||
unserem Impressum entnehmen.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Welche Daten werden verarbeitet</strong>
|
||||
<br />
|
||||
Rechtsgrundlagen der Datenverarbeitung Um Ihnen unsere Internetseite
|
||||
und die damit verbundenen Dienstleistungen anbieten zu können,
|
||||
verarbeiten wir personenbezogene Daten auf Basis folgender
|
||||
Rechtsgrundlagen:
|
||||
<ul>
|
||||
<li>Einwilligung (Art. 6 Abs. 1 lit. a) DSGVO</li>
|
||||
<li>zur Erfüllung von Verträgen (Art. 6 Abs. 1 lit. b) DSGVO</li>
|
||||
<li>
|
||||
auf Basis einer Interessenabwägung (Art. 6 Abs. 1 lit. f) DSGVO
|
||||
</li>
|
||||
<li>
|
||||
zur Erfüllung einer rechtlichen Verpflichtung (Art. 6 Abs. 1 lit.
|
||||
c) DSGVO
|
||||
</li>
|
||||
</ul>
|
||||
Wir werden im Zusammenhang mit der jeweiligen Verarbeitung auf die
|
||||
entsprechenden Begrifflichkeiten Bezug nehmen, so dass Sie einordnen
|
||||
können, auf welcher Basis wir personenbezogene Daten verarbeiten.
|
||||
<br />
|
||||
<br />
|
||||
Wenn personenbezogene Daten auf Grundlage einer Einwilligung von Ihnen
|
||||
verarbeitet werden, haben Sie das Recht, die Einwilligung jederzeit
|
||||
mit Wirkung für die Zukunft uns gegenüber zu widerrufen.
|
||||
<br />
|
||||
<br />
|
||||
Wenn wir Daten auf Basis einer Interessenabwägung verarbeiten, haben
|
||||
Sie als Betroffene/r das Recht, unter Berücksichtigung der Vorgaben
|
||||
von Art. 21 DSGVO der Verarbeitung der personenbezogenen Daten zu
|
||||
widersprechen.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Zugriffsdaten </strong>
|
||||
<br />
|
||||
Wenn Sie unsere Internetseiten besuchen, werden personenbezogene Daten
|
||||
verarbeitet, um Ihnen die Inhalte der Internetseite auf Ihrem Endgerät
|
||||
anzeigen zu können.
|
||||
<br />
|
||||
<br />
|
||||
Damit die Seiten in Ihrem Browser dargestellt werden können, muss die
|
||||
IP-Adresse des von Ihnen verwendeten Endgeräts verarbeitet werden.
|
||||
Hinzu kommen weitere Information über den Browser Ihres Endgeräts.
|
||||
<br />
|
||||
<br />
|
||||
Wir sind datenschutzrechtlich verpflichtet, auch die Vertraulichkeit
|
||||
und Integrität der mit unseren IT-Systemen verarbeiteten
|
||||
personenbezogenen Daten zu gewährleisten.
|
||||
<br />
|
||||
<br />
|
||||
Für diesen Zweck und aus diesem Interesse werden auf Basis einer
|
||||
Interessenabwägung nachfolgende Daten protokolliert:
|
||||
<br />
|
||||
<br />
|
||||
<ul>
|
||||
<li>IP-Adresse des aufrufenden Rechners (für maximal 7 Tage)</li>
|
||||
<li>Betriebssystem des aufrufenden Rechners</li>
|
||||
<li>Browser Version des aufrufenden Rechners</li>
|
||||
<li>Name der abgerufenen Datei</li>
|
||||
<li>Datum und Uhrzeit des Abrufs</li>
|
||||
<li>Übertragene Datenmenge</li>
|
||||
<li>Verweisende URL</li>
|
||||
</ul>
|
||||
Die IP-Adresse wird nach spätestens 7 Tagen von allen Systemen, die im
|
||||
Zusammenhang mit dem Betrieb dieser Internetseiten verwendet werden,
|
||||
gelöscht. Einen Personenbezug können wir aus den verbleibenden Daten
|
||||
dann nicht mehr herstellen.
|
||||
<br />
|
||||
<br />
|
||||
Die Daten werden ferner auch verwendet, um Fehler auf den
|
||||
Internetseiten ermitteln und beheben zu können.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Kontaktformular </strong>
|
||||
<br />
|
||||
Wir bieten auf unserer Internetseite ein Kontaktformular an, über das
|
||||
Sie Informationen zu unseren Produkten oder Dienstleistungen anfordern
|
||||
oder allgemein Kontakt aufnehmen können. Die von Ihnen zwingend zur
|
||||
Beantwortung einer Anfrage erforderlichen Daten haben wir als
|
||||
Pflichtfelder gekennzeichnet. Angaben zu weiteren Datenfeldern sind
|
||||
freiwillig.
|
||||
<br />
|
||||
<br />
|
||||
Wir benötigen diese Angaben, um Ihre Anfrage zu bearbeiten, Sie
|
||||
korrekt anzusprechen und Ihnen eine Antwort zukommen zu lassen. Die
|
||||
Datenverarbeitung erfolgt bei konkreten Anfragen zur Erfüllung eines
|
||||
Vertrages bzw. der Vertragsanbahnung. Bei allgemeinen Anfragen erfolgt
|
||||
die Verarbeitung auf Basis einer Interessenabwägung.
|
||||
<br />
|
||||
<br />
|
||||
Wir benötigen diese Angaben, um Ihre Anfrage zu bearbeiten, Sie
|
||||
korrekt anzusprechen und Ihnen eine Antwort zukommen zu lassen. Die
|
||||
Datenverarbeitung erfolgt bei konkreten Anfragen zur Erfüllung eines
|
||||
Vertrages bzw. der Vertragsanbahnung. Bei allgemeinen Anfragen erfolgt
|
||||
die Verarbeitung auf Basis einer Interessenabwägung.
|
||||
<br />
|
||||
<br />
|
||||
Anfragen, die über das Kontaktformular unserer Internetseite eingehen,
|
||||
werden bei uns elektronisch verarbeitet, um Ihre Anfrage zu
|
||||
beantworten. In dem Zusammenhang erhalten ggf. auch weitere Personen
|
||||
oder Abteilungen und ggf. Dritte Kenntnis von den Formularinhalten,
|
||||
die Sie übersendet haben.
|
||||
<br />
|
||||
<br />
|
||||
Die Übermittlung der Formular-Daten über das Internet erfolgt über
|
||||
verschlüsselte Verbindungen.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Newsletter </strong>
|
||||
Sie können auf unserer Internetseite auch einen E-Mail-Newsletter
|
||||
abonnieren. Wir verarbeiten neben den freiwilligen Angaben im
|
||||
jeweiligen Formular dazu nur Ihre E-Mail-Adresse. Diese ist allerdings
|
||||
auch zwingend erforderlich, um Ihnen den Newsletter zusenden zu
|
||||
können.
|
||||
<br />
|
||||
<br />
|
||||
Sie können den Newsletter jederzeit bei uns abbestellen. Alternativ
|
||||
finden Sie einen Link zur Abmeldung in jeder Newsletter-E-Mail.
|
||||
<br />
|
||||
<br />
|
||||
Um die Beliebtheit von unseren Newsletter-Aussendungen analysieren zu
|
||||
können und diese optimieren zu können, protokollieren wir, wenn
|
||||
E-Mails geöffnet und Links geklickt wurden. Diese Nutzungsanalyse
|
||||
erfolgt auf Basis einer Interessenabwägung. Sie können dieser
|
||||
Verarbeitung widersprechen, indem Sie sich von dem Newsletter
|
||||
abmelden.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Cookies </strong>
|
||||
<br />
|
||||
Auf unseren Internetseiten kommen Cookies zum Einsatz. Cookies sind
|
||||
kleine Textinformationen, die über ihren Browser in Ihrem Endgerät
|
||||
gespeichert werden. Die Cookies sind erforderlich, um bestimmte
|
||||
Funktionen unserer Internetseiten zu ermöglichen. Wir nutzen dabei nur
|
||||
Session-Cookies, die unmittelbar nach Beendigung des Besuchs der
|
||||
Internetseiten automatisch von Ihrem Browser gelöscht werden.
|
||||
<br />
|
||||
<br />
|
||||
Der Einsatz von Cookies erfolgt auf Basis einer Interessenabwägung.
|
||||
Unser Interesse ist der bedienungsfreundliche Besuch unserer
|
||||
Internetseiten.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Zwecke der Verarbeitung personenbezogener Daten </strong>
|
||||
<br />
|
||||
Die vorgenannten Daten verarbeiten wir für den Betrieb unserer
|
||||
Internetseite und für Erfüllung von vertraglichen Pflichten gegenüber
|
||||
unseren Kunden bzw. der Wahrung unserer berechtigten Interessen.
|
||||
<br />
|
||||
<br />
|
||||
Bei Anfragen von Ihnen außerhalb eines aktiven Kundenverhältnisses
|
||||
verarbeiten wir die Daten für Zwecke des Vertriebs und der Werbung.
|
||||
Sie können einer Verwendung Ihrer personenbezogenen Daten für
|
||||
Werbezwecke jederzeit widersprechen.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Freiwillige Angaben </strong>
|
||||
<br />
|
||||
Soweit Sie Daten uns gegenüber z.B. in Formularen freiwillig angeben
|
||||
und diese für die Erfüllung unserer vertraglichen Pflichten nicht
|
||||
erforderlich sind, verarbeiten wir diese Daten in der berechtigten
|
||||
Annahme, dass die Verarbeitung und Verwendung dieser Daten in Ihrem
|
||||
Interesse ist.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Empfänger / Weitergabe von Daten </strong>
|
||||
<br />
|
||||
Daten, die Sie uns gegenüber angeben, werden grundsätzlich nicht an
|
||||
Dritte weitergegeben. Insbesondere werden Ihre Daten nicht an Dritte
|
||||
für deren Werbezwecke weitergegeben.
|
||||
<br />
|
||||
<br />
|
||||
Wir setzen jedoch ggf. Dienstleister für den Betrieb dieser
|
||||
Internetseiten oder für weitere Produkte oder Dienstleistungen von uns
|
||||
ein. Hier kann es vorkommen, dass ein Dienstleister Kenntnis von
|
||||
personenbezogenen Daten erhält Wir wählen unsere Dienstleister
|
||||
sorgfältig – insbesondere im Hinblick auf Datenschutz und
|
||||
Datensicherheit – aus und treffen alle datenschutzrechtlich
|
||||
erforderlichen Maßnahmen für eine zulässige Datenverarbeitung.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Datenverarbeitung außerhalb Europäischer Union </strong>
|
||||
Soweit personenbezogene Daten außerhalb der europäischen Union
|
||||
verarbeitet werden, können Sie dies den vorherigen Ausführungen
|
||||
entnehmen.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong>Datenschutzbeauftragter </strong>
|
||||
<br />
|
||||
Wir haben einen Datenschutzbeauftragten benannt.
|
||||
<br />
|
||||
<br />
|
||||
Sie erreichen diesen wie folgt:
|
||||
<br />
|
||||
<br />
|
||||
Aurora Data GmbH
|
||||
<br />
|
||||
Gebrüder-Schönthal-Str. 37
|
||||
<br />
|
||||
48432 Rheine
|
||||
<br />
|
||||
<br />
|
||||
Tel. 05975 955 8 455
|
||||
<br />
|
||||
E-Mail: datenschutz@aurora-data.de
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong>Ihre Rechte als Betroffener</strong>
|
||||
<br />
|
||||
Sie haben das Recht auf Auskunft über die Sie betreffenden
|
||||
personenbezogenen Daten. Sie können sich für eine Auskunft jederzeit
|
||||
an uns wenden.
|
||||
<br />
|
||||
<br />
|
||||
Bei einer Auskunftsanfrage, die nicht schriftlich erfolgt, bitten wir
|
||||
um Verständnis dafür, dass wir ggf. Nachweise von Ihnen verlangen, die
|
||||
belegen, dass Sie die Person sind, für die Sie sich ausgeben.
|
||||
<br />
|
||||
<br />
|
||||
Ferner haben Sie ein Recht auf Berichtigung oder Löschung oder auf
|
||||
Einschränkung der Verarbeitung, soweit Ihnen dies gesetzlich zusteht.
|
||||
<br />
|
||||
<br />
|
||||
Schließlich haben Sie ein Widerspruchsrecht gegen die Verarbeitung im
|
||||
Rahmen der gesetzlichen Vorgaben.
|
||||
<br />
|
||||
<br />
|
||||
Ein Recht auf Datenübertragbarkeit besteht ebenfalls im Rahmen der
|
||||
datenschutz-rechtlichen Vorgaben.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Löschung von Daten </strong>
|
||||
<br />
|
||||
Wir löschen personenbezogene Daten grundsätzlich dann, wenn kein
|
||||
Erfordernis für eine weitere Speicherung besteht. Ein Erfordernis kann
|
||||
insbesondere dann bestehen, wenn die Daten noch benötigt werden, um
|
||||
vertragliche Leistungen zur erfüllen, Gewährleistungs- und ggf.
|
||||
Garantieansprüche prüfen und gewähren oder abwehren zu können. Im
|
||||
Falle von gesetzlichen Aufbewahrungspflichten kommt eine Löschung erst
|
||||
nach Ablauf der jeweiligen Aufbewahrungspflicht in Betracht.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Beschwerderecht bei einer Aufsichtsbehörde </strong>
|
||||
<br />
|
||||
Sie haben das Recht, sich über die Verarbeitung personenbezogenen
|
||||
Daten durch uns bei einer Aufsichtsbehörde für den Datenschutz zu
|
||||
beschweren.
|
||||
<Divider sx={dividerStyle} />
|
||||
<strong> Änderung dieser Datenschutzhinweise </strong>
|
||||
<br />
|
||||
Wir überarbeiten diese Datenschutzhinweise bei Änderungen an dieser
|
||||
Internetseite oder bei sonstigen Anlässen, die dies erforderlich
|
||||
machen. Die jeweils aktuelle Fassung finden sie stets auf der
|
||||
Internetseite.
|
||||
<Divider sx={dividerStyle} />
|
||||
<Box
|
||||
style={{
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Stand: 07/2021
|
||||
</Box>
|
||||
</Typography>
|
||||
</Box>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Datenschutz;
|
|
@ -1,142 +0,0 @@
|
|||
import { Box, Divider, Grid, SxProps, Theme, Typography } from "@mui/material";
|
||||
import TopBar from "../components/TopBar";
|
||||
import { Footer } from "../components/Footer";
|
||||
import { useEffect } from "react";
|
||||
|
||||
let dividerStyle: SxProps<Theme> = {
|
||||
my: "20px",
|
||||
width: "100%",
|
||||
background: (theme) => theme.palette.secondary.main,
|
||||
};
|
||||
|
||||
function Impressum() {
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
px: "10vw",
|
||||
mt: "5vh",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "800",
|
||||
mb: "5vh",
|
||||
fontSize: "3rem",
|
||||
}}
|
||||
>
|
||||
Impressum
|
||||
</Typography>
|
||||
|
||||
<Divider sx={dividerStyle} />
|
||||
|
||||
<Grid container sx={{ width: "100%", height: "auto" }} spacing={5}>
|
||||
<Grid item lg={4}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "light",
|
||||
mb: "5vh",
|
||||
fontSize: "1rem",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<strong> RF Computer GmbH & Co. KG </strong>
|
||||
<br />
|
||||
<br />
|
||||
<strong> Ralf Fink </strong>
|
||||
<br />
|
||||
<br />
|
||||
Otto-Bergmeyer-Str. 7
|
||||
<br />
|
||||
48431 Rheine
|
||||
<br />
|
||||
<br />
|
||||
Tel. 05971-911 11-0
|
||||
<br />
|
||||
Fax 05971-911 1119
|
||||
<br />
|
||||
www.rf-computer.de
|
||||
<br />
|
||||
mail: info@rf-computer.de
|
||||
<br />
|
||||
<br />
|
||||
USt-Id. -NR. DE345175566
|
||||
<br />
|
||||
<br />
|
||||
Steuer-Nr. 311/5991/2680
|
||||
<br />
|
||||
<br />
|
||||
AG Steinfurt HRA 7718
|
||||
<br />
|
||||
<br />
|
||||
Verantwortlich für journalistisch-redaktionelle Inhalte i.S.d. §
|
||||
18 Abs. 2 MStV: Ralf Fink
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item lg={4}>
|
||||
<Typography>
|
||||
<strong>
|
||||
{" "}
|
||||
© Copyright 2015-2019, RF Computer Alle Rechte vorbehalten.
|
||||
</strong>
|
||||
<br />
|
||||
<br />
|
||||
Gewährleistung
|
||||
<br />
|
||||
RF Computer verwendet die erforderliche Sorgfalt, um die
|
||||
Informationen der Website aktuell und korrekt zu halten. Dennoch
|
||||
kann nicht ausgeschlossen werden, dass Irrtümer auftreten oder
|
||||
Informationen unrichtig wiedergegeben werden. Besucher sollten aus
|
||||
diesem Grund die Richtigkeit und Vollständigkeit der Informationen
|
||||
nicht annehmen, sondern direkt mit RF Computer in Kontakt treten
|
||||
und die Angaben prüfen lassen.
|
||||
<br />
|
||||
<br />
|
||||
RF Computer hat keine Möglichkeit, zu kontrollieren, wie die
|
||||
Inhalte der Website verwendet oder beim Besucher angezeigt werden.
|
||||
RF Computer kann daher keine Haftung für unmittelbare und
|
||||
mittelbare Schäden, die aus diesen Informationen und / oder ihrer
|
||||
Verwendung entstehen, übernehmen.
|
||||
<br />
|
||||
<br />
|
||||
RF Computer kann nicht alle Webseiten, die mit der eigenen Website
|
||||
verlinkt sind, auf Ihren Inhalt überprüfen. Eine Haftung für die
|
||||
Inhalte fremder Websites ist daher in jedem Fall ausgeschlossen.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item lg={4}>
|
||||
<Typography>
|
||||
<strong>Hinweis für Verbraucher:</strong>
|
||||
<br />
|
||||
<br />
|
||||
Plattform der Europäischen Kommission zur Online-Streitbeilegung
|
||||
(OS) für Verbraucher: https://ec.europa.eu/consumers/odr Wir sind
|
||||
nicht bereit und nicht verpflichtet, an einem
|
||||
Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle
|
||||
teilzunehmen.
|
||||
<br />
|
||||
<br />
|
||||
<strong>Urheberrecht</strong>
|
||||
<br />
|
||||
<br />
|
||||
RF Computer erteilt die Erlaubnis, alle auf der Website
|
||||
veröffentlichten Inhalte zur Informationsgewinnung zu nutzen, zu
|
||||
kopieren oder einen Ausdruck zu erstellen, sofern ein
|
||||
entsprechender Copyrighthinweis auf RF Computer erfolgt.
|
||||
Keinesfalls dürfen diese Inhalte jedoch verändert, zu
|
||||
Geschäftszwecken oder auf anderen Websites verwendet werden.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Impressum;
|
|
@ -1,16 +1,10 @@
|
|||
import { Typography, Button, Box, Grid, CircularProgress } from "@mui/material";
|
||||
import { DescriptionOutlined, BadgeOutlined } from "@mui/icons-material";
|
||||
import { AboutSection } from "../components/AboutSection";
|
||||
import { AboutSection2 } from "../components/AboutSection2";
|
||||
import { Footer } from "../components/Footer";
|
||||
import TopBar from "../components/TopBar";
|
||||
import { BadgeOutlined } from "@mui/icons-material";
|
||||
import { useTheme } from "@mui/material";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
import SponsorCard from "../components/SponsorCard";
|
||||
import axios from "axios";
|
||||
import { getBaseURL } from "../functions";
|
||||
import ArticleCard from "../components/ArticleCard";
|
||||
|
||||
function LandingPage() {
|
||||
const theme = useTheme();
|
||||
|
@ -24,7 +18,6 @@ function LandingPage() {
|
|||
}, [location]);
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
|
@ -133,155 +126,8 @@ function LandingPage() {
|
|||
Sponsor Werden!
|
||||
</Button>
|
||||
</Box>
|
||||
<Sponsors />
|
||||
<AboutSection />
|
||||
<AboutSection2 />
|
||||
<Articles />
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default LandingPage;
|
||||
|
||||
function Sponsors(): JSX.Element {
|
||||
const [data, setData] = useState<Types.Sponsor[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${getBaseURL()}/api/sponsors/`).then((res) => {
|
||||
setData(res.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (data === null)
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
px: "10px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
id="sponsors"
|
||||
container
|
||||
sx={{
|
||||
width: "100%",
|
||||
py: "10vh",
|
||||
}}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bolder",
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "4rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "center",
|
||||
color: "primary.main",
|
||||
mb: "5vh",
|
||||
}}
|
||||
>
|
||||
Unsere Sponsoren
|
||||
</Typography>
|
||||
</Grid>
|
||||
{data.map((sponsor) => (
|
||||
<Grid
|
||||
item
|
||||
xs
|
||||
key={sponsor.ID}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<SponsorCard
|
||||
id={sponsor.ID}
|
||||
name={sponsor.name}
|
||||
description={sponsor.description}
|
||||
url={sponsor.url}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
function Articles(): JSX.Element {
|
||||
const [data, setData] = useState<Types.Article[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${getBaseURL()}/api/articles/public`).then((res) => {
|
||||
setData(res.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (data === null)
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
px: "10px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
id="sponsors"
|
||||
container
|
||||
sx={{
|
||||
width: "100%",
|
||||
py: "10vh",
|
||||
}}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bolder",
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "4rem",
|
||||
letterSpacing: "0.05rem",
|
||||
lineHeight: "1.2",
|
||||
textAlign: "center",
|
||||
color: "primary.main",
|
||||
mb: "5vh",
|
||||
}}
|
||||
>
|
||||
Aktuelles
|
||||
</Typography>
|
||||
</Grid>
|
||||
{data.map((sponsor) => (
|
||||
<Grid
|
||||
item
|
||||
xs
|
||||
key={sponsor.ID}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ArticleCard
|
||||
id={sponsor.ID}
|
||||
name={sponsor.title}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
export default LandingPage;
|
|
@ -1,431 +0,0 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import SunEditor from "suneditor-react";
|
||||
import SunEditorCore from "suneditor/src/lib/core";
|
||||
import "suneditor/dist/css/suneditor.min.css"; // Import Sun Editor's CSS File
|
||||
import {
|
||||
fontSize,
|
||||
formatBlock,
|
||||
image,
|
||||
video,
|
||||
table,
|
||||
list,
|
||||
align,
|
||||
} from "suneditor/src/plugins";
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
Typography,
|
||||
Grid,
|
||||
Backdrop,
|
||||
Menu,
|
||||
Autocomplete,
|
||||
TextField,
|
||||
Avatar,
|
||||
} from "@mui/material";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { AddBox } from "@mui/icons-material";
|
||||
import { getBaseURL } from "../../functions";
|
||||
import axios from "axios";
|
||||
import { SponsorImageSmall } from "../../components/SponsorImageSmall";
|
||||
|
||||
function ArticleEditor() {
|
||||
const navigate = useNavigate();
|
||||
const editor = useRef<SunEditorCore>();
|
||||
|
||||
// The sunEditor parameter will be set to the core suneditor instance when this function is called
|
||||
const getSunEditorInstance = (sunEditor: SunEditorCore) => {
|
||||
editor.current = sunEditor;
|
||||
sunEditor.save();
|
||||
};
|
||||
|
||||
let { id } = useParams() as {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const upLoadInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const [banner, setBanner] = useState<string>("");
|
||||
|
||||
const [sponsorsAvail, setSponsorsAvail] = useState<Types.Sponsor[]>([]);
|
||||
const [sponsorsSelected, setSponsorsSelected] = useState<Types.Sponsor[]>([]);
|
||||
|
||||
const sponsorsSelectedRef = useRef<Types.Sponsor[]>([]);
|
||||
|
||||
sponsorsSelectedRef.current = sponsorsSelected;
|
||||
|
||||
const [sponsorMenuEl, setSponsorMenuEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
const loadbanner = () => {
|
||||
if (!id) setBanner("https://placehold.co/1920x1080");
|
||||
else setBanner(`${getBaseURL()}/api/article/banner/${id}`);
|
||||
};
|
||||
|
||||
const save = async (content: string) => {
|
||||
const title = document.getElementById("article-title")?.innerText;
|
||||
let banner = document
|
||||
.getElementById("articleBanner-content")
|
||||
?.getAttribute("src");
|
||||
if (!title) return false;
|
||||
if (!banner) return false;
|
||||
|
||||
if (banner.startsWith("http")) banner = "stale";
|
||||
|
||||
setSaving(true);
|
||||
const res = await axios
|
||||
.post(
|
||||
!id
|
||||
? `${getBaseURL()}/api/article/create`
|
||||
: `${getBaseURL()}/api/article/edit/${id}`,
|
||||
{
|
||||
title: title,
|
||||
sponsors: sponsorsSelectedRef.current.map((e) => e.ID),
|
||||
content: content,
|
||||
image: banner,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setSaving(false);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!res) return;
|
||||
|
||||
navigate(`/admin/editor/${res.data.ID}`);
|
||||
id = res.data.ID;
|
||||
|
||||
setSaving(false);
|
||||
return true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${getBaseURL()}/api/sponsors`).then((res) => {
|
||||
if (!res.data) return;
|
||||
|
||||
setSponsorsAvail(res.data);
|
||||
});
|
||||
|
||||
|
||||
if (!id) return;
|
||||
|
||||
setLoading(true);
|
||||
loadbanner();
|
||||
|
||||
axios.get(`${getBaseURL()}/api/article/view/${id}`).then((res) => {
|
||||
if (!res.data) return;
|
||||
|
||||
document.getElementById("article-title")!.innerText = res.data.title;
|
||||
setSponsorsSelected(res.data.sponsors);
|
||||
|
||||
axios.get(`${getBaseURL()}/api/article/content/${id}`).then((res) => {
|
||||
if (!res.data) return;
|
||||
|
||||
editor.current?.setContents(res.data);
|
||||
setTimeout(() => {
|
||||
// get all images with a data-size property
|
||||
const images = document.querySelectorAll("img[data-size]");
|
||||
console.log(images);
|
||||
|
||||
// loop through all images and set the width style to the data-size property
|
||||
for (let image of [...images]) {
|
||||
(image as HTMLElement).style.width =
|
||||
image.getAttribute("data-size")?.replaceAll(",", "") ?? "auto";
|
||||
}
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu
|
||||
anchorEl={sponsorMenuEl}
|
||||
open={Boolean(sponsorMenuEl)}
|
||||
onClose={() => {
|
||||
setSponsorMenuEl(null);
|
||||
}}
|
||||
>
|
||||
<Autocomplete
|
||||
options={sponsorsAvail.filter(e => !sponsorsSelected.map(e => e.ID).includes(e.ID))}
|
||||
getOptionLabel={(option) => option.name}
|
||||
renderOption={(props, option) => (
|
||||
<Box
|
||||
component="li"
|
||||
sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
|
||||
{...props}
|
||||
onClick={() => {
|
||||
sponsorsSelected.push(option as Types.Sponsor);
|
||||
setSponsorsSelected(sponsorsSelected);
|
||||
setSponsorMenuEl(null);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={`${getBaseURL()}/api/sponsors/logo/${option.ID}`}
|
||||
sx={{
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
borderRadius: "20px",
|
||||
}}
|
||||
/>
|
||||
{option.name}
|
||||
</Box>
|
||||
)}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
sx={{
|
||||
width: "300px",
|
||||
}}
|
||||
fullWidth
|
||||
label="Sponsoren"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Menu>
|
||||
{loading && <CircularProgress />}
|
||||
{!loading && (
|
||||
<Box
|
||||
className="articleBanner"
|
||||
sx={{
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-start",
|
||||
zIndex: 0,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
upLoadInputRef.current?.click();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={banner}
|
||||
id="articleBanner-content"
|
||||
alt=""
|
||||
style={{
|
||||
filter: "brightness(0.5)",
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
zIndex: -1,
|
||||
objectFit: "cover",
|
||||
minWidth: "100vw",
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={upLoadInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
setBanner(reader.result as string);
|
||||
};
|
||||
}}
|
||||
onAbort={() => {}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
<Backdrop
|
||||
open={saving}
|
||||
sx={{
|
||||
zIndex: 9999,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
backgroundColor: "#fff",
|
||||
padding: "20px",
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "20px",
|
||||
fontWeight: 700,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Wird Gespeichert...
|
||||
</Typography>
|
||||
</Box>
|
||||
</Backdrop>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
|
||||
px: "15%",
|
||||
|
||||
"@media screen and (max-width: 768px)": {
|
||||
px: "0px",
|
||||
},
|
||||
|
||||
"@media screen and (max-width: 1300px)": {
|
||||
px: "2px",
|
||||
},
|
||||
|
||||
"@media screen and (min-width: 1800px)": {
|
||||
px: "15%",
|
||||
},
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
|
||||
transform: "translateY(25vh)",
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "1080px",
|
||||
height: "fit-content",
|
||||
boxShadow: "0px 0px 10px 0px rgba(0,0,0,0.5)",
|
||||
opacity: loading ? 0 : 1,
|
||||
padding: "40px",
|
||||
backgroundColor: "white",
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h1"
|
||||
contentEditable
|
||||
id="article-title"
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "3rem",
|
||||
fontWeight: 700,
|
||||
textAlign: "left",
|
||||
mb: "20px",
|
||||
}}
|
||||
>
|
||||
Titel
|
||||
</Typography>
|
||||
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
sx={{
|
||||
pb: "20px",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
{sponsorsSelected.map((sponsor) => (
|
||||
<SponsorImageSmall
|
||||
name={sponsor.name}
|
||||
image={`${getBaseURL()}/api/sponsors/logo/${sponsor.ID}`}
|
||||
description={sponsor.description}
|
||||
link={sponsor.url}
|
||||
onRemove={() => {
|
||||
setSponsorsSelected(
|
||||
sponsorsSelected.filter((e) => e.ID !== sponsor.ID)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Grid item>
|
||||
<AddBox
|
||||
fontSize="large"
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
|
||||
transition: "all 0.2s ease-in-out",
|
||||
"&:hover": {
|
||||
color: "#000000AA",
|
||||
transform: "scale(1.1)",
|
||||
},
|
||||
}}
|
||||
onClick={(e) => {
|
||||
setSponsorMenuEl(e.currentTarget as any);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<SunEditor
|
||||
lang="de"
|
||||
setDefaultStyle="font-family: 'Lexend Variable';"
|
||||
getSunEditorInstance={getSunEditorInstance}
|
||||
onSave={save}
|
||||
setOptions={{
|
||||
plugins: [
|
||||
image,
|
||||
fontSize,
|
||||
formatBlock,
|
||||
video,
|
||||
table,
|
||||
list,
|
||||
align,
|
||||
],
|
||||
font: ["Lexend Variable"],
|
||||
defaultStyle: "font-family: 'Lexend Variable';",
|
||||
stickyToolbar: "0",
|
||||
buttonList: [
|
||||
["save"],
|
||||
["undo", "redo"],
|
||||
["fontSize", "formatBlock"],
|
||||
[
|
||||
"bold",
|
||||
"underline",
|
||||
"italic",
|
||||
"strike",
|
||||
"subscript",
|
||||
"superscript",
|
||||
],
|
||||
["removeFormat"],
|
||||
["outdent", "indent"],
|
||||
["align", "list", "table"],
|
||||
["image", "video"],
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArticleEditor;
|
||||
|
||||
|
|
@ -1,245 +0,0 @@
|
|||
import { Add, Delete, Edit } from "@mui/icons-material";
|
||||
import {
|
||||
Backdrop,
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Divider,
|
||||
IconButton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import axios from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { formatNum, getBaseURL } from "../../functions";
|
||||
import moment from "moment";
|
||||
|
||||
function Artikel() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [articles, setArticles] = useState<Types.Article[]>([]);
|
||||
const [loaded, setLoaded] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLoaded(false);
|
||||
|
||||
axios
|
||||
.get(`${getBaseURL()}/api/allArticles`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
setArticles(res.data);
|
||||
setLoaded(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
|
||||
pt: "30px",
|
||||
px: "30px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "40px",
|
||||
fontWeight: "bold",
|
||||
ml: "20px",
|
||||
}}
|
||||
>
|
||||
Artikel
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{}}
|
||||
onClick={() => {
|
||||
navigate("/admin/editor");
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
<Add /> Neu{" "}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Divider
|
||||
sx={{
|
||||
width: "100%",
|
||||
backgroundColor: "#000000AA",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Table
|
||||
sx={{
|
||||
width: "100%",
|
||||
mt: "30px",
|
||||
}}
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="left" width="700px">
|
||||
Titel
|
||||
</TableCell>
|
||||
<TableCell align="right">Autor</TableCell>
|
||||
<TableCell align="right">Aufrufe</TableCell>
|
||||
<TableCell align="right">Datum</TableCell>
|
||||
<TableCell align="right">Optionen</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<Backdrop
|
||||
open={!loaded}
|
||||
sx={{
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1,
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
/>
|
||||
</Backdrop>
|
||||
{loaded &&
|
||||
articles.map((article) => (
|
||||
<TableRow>
|
||||
<TableCell align="left">
|
||||
<Link
|
||||
target="_blank"
|
||||
to={`/artikel/${article.ID}`}>
|
||||
{article.title}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell align="right">{article.author.username}</TableCell>
|
||||
<TableCell align="right">{formatNum(article.views)}</TableCell>
|
||||
<TableCell align="right" width="200px" sx={{}}>
|
||||
<Tooltip
|
||||
arrow
|
||||
placement="top"
|
||||
title={moment(article.createdAt).locale("de").fromNow()}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
width: "fit-content",
|
||||
marginLeft: "auto",
|
||||
}}
|
||||
>
|
||||
{moment(article.createdAt).locale("de").format("ll")}
|
||||
</p>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="right" width="60px">
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
|
||||
padding: "5px",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/admin/editor/${article.ID}`);
|
||||
}}
|
||||
>
|
||||
|
||||
<Edit
|
||||
sx={{
|
||||
fontSize: "20px",
|
||||
color: "#000000CC",
|
||||
}}
|
||||
|
||||
/>
|
||||
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
|
||||
padding: "5px",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: "5px",
|
||||
"&:hover": {
|
||||
background: "red",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
setLoaded(false);
|
||||
axios
|
||||
.delete(`${getBaseURL()}/api/article/${article.ID}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem(
|
||||
"token"
|
||||
)}`,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
setArticles(
|
||||
articles.filter(
|
||||
(e) => e.ID !== article.ID
|
||||
)
|
||||
);
|
||||
setLoaded(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Delete
|
||||
sx={{
|
||||
fontSize: "20px",
|
||||
color: "#000000CC",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Artikel;
|
|
@ -4,31 +4,64 @@ import { OverridableComponent } from "@mui/material/OverridableComponent";
|
|||
import axios from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getBaseURL } from "../../functions";
|
||||
import { DataGrid, GridColDef, GridValueGetterParams } from "@mui/x-data-grid";
|
||||
import moment from "moment";
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: "Vorname",
|
||||
headerName: "Vorname",
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: "Nachname",
|
||||
headerName: "Nachname",
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: "Anstelldatum",
|
||||
headerName: "Anstelldatum",
|
||||
type: "date",
|
||||
width: 160,
|
||||
valueFormatter: params => moment(params.value).format("DD.MM.YYYY")
|
||||
|
||||
},
|
||||
{
|
||||
field: "Geburtstag",
|
||||
headerName: "Geburtstag",
|
||||
type: "date",
|
||||
width: 160,
|
||||
valueFormatter: params => moment(params.value).format("DD.MM.YYYY")
|
||||
|
||||
},
|
||||
];
|
||||
|
||||
function Dashboard() {
|
||||
const [stats, setStats] = useState<{
|
||||
users: number;
|
||||
articles: number;
|
||||
sponsors: number;
|
||||
views: number;
|
||||
}>({
|
||||
users: 0,
|
||||
articles: 0,
|
||||
sponsors: 0,
|
||||
views: 0,
|
||||
});
|
||||
const [workers, setWorkers] = useState<Types.Worker[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${getBaseURL()}/api/stats`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
},
|
||||
}).then((response) => {
|
||||
console.log(response.data);
|
||||
setStats(response.data);
|
||||
});
|
||||
axios
|
||||
.get(`${getBaseURL()}/api/workers`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
setWorkers(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const rows = workers.map((worker, index) => ({
|
||||
id: index,
|
||||
Vorname: worker.Vorname,
|
||||
Nachname: worker.Nachname,
|
||||
Geburtstag: worker.Geburtstag,
|
||||
Anstelldatum: worker.Anstelldatum,
|
||||
}))
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -44,100 +77,39 @@ function Dashboard() {
|
|||
px: "30px",
|
||||
}}
|
||||
>
|
||||
<Grid container spacing={1}>
|
||||
<StatDisplay Title="Benutzer" Icon={Person} Value={stats.users.toFixed(0)} />
|
||||
<StatDisplay Title="Artikel" Icon={Newspaper} Value={stats.articles.toFixed(0)} />
|
||||
<StatDisplay Title="Sponsoren" Icon={Savings} Value={stats.sponsors.toFixed(0)} />
|
||||
<StatDisplay Title="Views" Icon={Visibility} Value={stats.views.toFixed(0)} />
|
||||
</Grid>
|
||||
<Typography variant="h4" sx={{ fontWeight: "bold" }}>
|
||||
Nächste Geburtstage
|
||||
</Typography>
|
||||
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
sx={{
|
||||
fontSize: "1rem",
|
||||
marginTop: "1rem",
|
||||
borderRadius: "20px",
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
}}
|
||||
initialState={{
|
||||
sorting: {
|
||||
sortModel: [
|
||||
{
|
||||
field: 'commodity',
|
||||
sort: 'asc',
|
||||
},
|
||||
],
|
||||
},
|
||||
pagination: {
|
||||
paginationModel: {
|
||||
pageSize: 15,
|
||||
},
|
||||
},
|
||||
}}
|
||||
checkboxSelection
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
function StatDisplay({
|
||||
Title,
|
||||
Icon,
|
||||
Value,
|
||||
}: {
|
||||
Title: string;
|
||||
Icon: OverridableComponent<SvgIconTypeMap<{}, "svg">> & {
|
||||
muiName: string;
|
||||
};
|
||||
Value: string;
|
||||
}) {
|
||||
return (
|
||||
<Grid item lg={3} md={6} sm={12}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
padding: "10px",
|
||||
background: (theme) =>
|
||||
`linear-gradient(45deg, ${theme.palette.secondary.light}, ${theme.palette.secondary.main})`,
|
||||
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "24px",
|
||||
color: "#000000AA",
|
||||
}}
|
||||
>
|
||||
{Title}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
|
||||
padding: "5px",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
sx={{
|
||||
fontSize: "40px",
|
||||
color: "#000000CC",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "40px",
|
||||
color: "#000000FF",
|
||||
}}
|
||||
>
|
||||
{Value}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import React, { useState, ChangeEvent, FormEvent } from "react";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||
import moment from "moment";
|
||||
|
||||
function Import() {
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [csvData, setCsvData] = useState<
|
||||
Array<{ [key: string]: string } | null>
|
||||
>([]);
|
||||
|
||||
const fileReader = new FileReader();
|
||||
|
||||
const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
setFile(e.target.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const csvFileToArray = (string: string) => {
|
||||
const csvRows = string.split("\n");
|
||||
const csvHeader = csvRows[0].split(";");
|
||||
const newArray = csvRows
|
||||
.slice(1)
|
||||
.map((row) => {
|
||||
const values = row.split(";");
|
||||
if (values.length === csvHeader.length) {
|
||||
const obj: { [key: string]: string } = {};
|
||||
csvHeader.forEach((header, index) => {
|
||||
obj[header] = values[index] ? values[index].replace(/"/g, "") : "";
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
setCsvData(newArray);
|
||||
};
|
||||
|
||||
const handleOnSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (file) {
|
||||
fileReader.onload = function (event) {
|
||||
if (event.target?.result) {
|
||||
const text = event.target.result.toString();
|
||||
csvFileToArray(text);
|
||||
}
|
||||
};
|
||||
|
||||
fileReader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ textAlign: "center" }}>
|
||||
<Typography variant="h1">CSV IMPORT</Typography>
|
||||
<form>
|
||||
<input
|
||||
type="file"
|
||||
id="csvFileInput"
|
||||
accept=".csv"
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
<Button onClick={(e) => handleOnSubmit(e)}>IMPORT CSV</Button>
|
||||
</form>
|
||||
<br />
|
||||
{csvData.length > 0 ? (
|
||||
<div>
|
||||
<Typography variant="h4">Imported Data</Typography>
|
||||
<DataGrid
|
||||
rows={csvData.map((row, index) => ({ id: index, ...row }))}
|
||||
columns={
|
||||
csvData.length > 0
|
||||
? Object.keys(csvData[0] || {}).map(
|
||||
(header) =>
|
||||
({
|
||||
field: header,
|
||||
headerName: header.replace(/"/g, ""),
|
||||
width: 150,
|
||||
valueFormatter(params) {
|
||||
if (params.value) {
|
||||
if (moment(params.value, "DD/MM/YYYY").isValid()) {
|
||||
return moment(params.value).format(
|
||||
"DD/MM/YYYY"
|
||||
);
|
||||
}
|
||||
}
|
||||
return params.value;
|
||||
},
|
||||
|
||||
|
||||
} as GridColDef)
|
||||
)
|
||||
: []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p>No data to display</p>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Import;
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
|
||||
function Kalender() {
|
||||
return (
|
||||
<div className="container">
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Kalender;
|
|
@ -1,232 +0,0 @@
|
|||
import {
|
||||
ManageAccounts,
|
||||
DomainDisabledRounded,
|
||||
DomainAddRounded,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
IconButton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Avatar,
|
||||
Backdrop,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import SponsorModal from "../../components/SponsorModal";
|
||||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { getBaseURL } from "../../functions";
|
||||
import moment from "moment";
|
||||
|
||||
function Sponsoren() {
|
||||
const [modalID, setModelID] = useState<string | null>(null);
|
||||
|
||||
const [sponsors, setSponsors] = useState<Types.Sponsor[]>([]);
|
||||
const [loaded, setLoaded] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLoaded(false);
|
||||
axios.get(`${getBaseURL()}/api/sponsors/`).then((res) => {
|
||||
setSponsors(res.data);
|
||||
setLoaded(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Backdrop
|
||||
open={!loaded}
|
||||
sx={{
|
||||
zIndex: 2000,
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
/>
|
||||
</Backdrop>
|
||||
<SponsorModal
|
||||
id={modalID}
|
||||
onClose={() => {
|
||||
setModelID(null);
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
|
||||
pt: "30px",
|
||||
px: "30px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "Lexend Variable",
|
||||
fontSize: "40px",
|
||||
fontWeight: "bold",
|
||||
ml: "20px",
|
||||
}}
|
||||
>
|
||||
Sponsoren
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
onClick={() => {
|
||||
setModelID("create");
|
||||
}}
|
||||
>
|
||||
<DomainAddRounded /> Neue Sponsoren
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Divider
|
||||
sx={{
|
||||
width: "100%",
|
||||
backgroundColor: "#000000AA",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Table
|
||||
sx={{
|
||||
width: "100%",
|
||||
mt: "30px",
|
||||
}}
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="left" width="50px">
|
||||
{" "}
|
||||
</TableCell>
|
||||
<TableCell align="left" width="100%">
|
||||
Name
|
||||
</TableCell>
|
||||
<TableCell align="right">Datum</TableCell>
|
||||
<TableCell align="right">Optionen</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{sponsors.map((sponsor) => (
|
||||
<TableRow key={sponsor.ID}>
|
||||
<TableCell align="left" width="50px" padding="none">
|
||||
<Avatar
|
||||
src={`${getBaseURL()}/api/sponsors/logo/${sponsor.ID}`}
|
||||
alt="logo"
|
||||
style={{
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
borderRadius: "20px",
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="left">{sponsor.name}</TableCell>
|
||||
<TableCell align="right" width="200px">
|
||||
{moment(sponsor.addedAt).locale("de").format("ll")}
|
||||
</TableCell>
|
||||
<TableCell align="right" width="60px">
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Daten anpassen">
|
||||
<IconButton
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
|
||||
padding: "5px",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
onClick={() => {
|
||||
setModelID(sponsor.ID);
|
||||
}}
|
||||
>
|
||||
<ManageAccounts />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Sponsor löschen">
|
||||
<IconButton
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
|
||||
padding: "5px",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: "5px",
|
||||
"&:hover": {
|
||||
background: "red",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
axios
|
||||
.delete(
|
||||
`${getBaseURL()}/api/sponsors/delete/${
|
||||
sponsor.ID
|
||||
}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem(
|
||||
"token"
|
||||
)}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
setSponsors(
|
||||
sponsors.filter((s) => s.ID !== sponsor.ID)
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DomainDisabledRounded />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sponsoren;
|
|
@ -1,15 +1,9 @@
|
|||
declare namespace Types {
|
||||
interface Article {
|
||||
ID: string;
|
||||
title: string;
|
||||
views: number;
|
||||
author: {
|
||||
ID: string;
|
||||
username: string;
|
||||
}
|
||||
sponsors: Sponsor[];
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
interface Worker {
|
||||
Vorname: string;
|
||||
Nachname: string;
|
||||
Geburtstag: Date;
|
||||
Anstelldatum: Date;
|
||||
}
|
||||
interface User extends UserPermissions {
|
||||
ID: string;
|
||||
|
|
|
@ -0,0 +1,657 @@
|
|||
{
|
||||
"name": "manager",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "manager",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@inquirer/prompts": "^3.3.0",
|
||||
"chalk": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/inquirer": "^9.0.7",
|
||||
"@types/node": "^20.11.5",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/checkbox": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-1.5.0.tgz",
|
||||
"integrity": "sha512-3cKJkW1vIZAs4NaS0reFsnpAjP0azffYII4I2R7PTI7ZTMg5Y1at4vzXccOH3762b2c2L4drBhpJpf9uiaGNxA==",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^5.1.1",
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"ansi-escapes": "^4.3.2",
|
||||
"chalk": "^4.1.2",
|
||||
"figures": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/checkbox/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/confirm": {
|
||||
"version": "2.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-2.0.15.tgz",
|
||||
"integrity": "sha512-hj8Q/z7sQXsF0DSpLQZVDhWYGN6KLM/gNjjqGkpKwBzljbQofGjn0ueHADy4HUY+OqDHmXuwk/bY+tZyIuuB0w==",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^5.1.1",
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"chalk": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/confirm/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/core": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-5.1.1.tgz",
|
||||
"integrity": "sha512-IuJyZQUg75+L5AmopgnzxYrgcU6PJKL0hoIs332G1Gv55CnmZrhG6BzNOeZ5sOsTi1YCGOopw4rYICv74ejMFg==",
|
||||
"dependencies": {
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"@types/mute-stream": "^0.0.4",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/wrap-ansi": "^3.0.0",
|
||||
"ansi-escapes": "^4.3.2",
|
||||
"chalk": "^4.1.2",
|
||||
"cli-spinners": "^2.9.1",
|
||||
"cli-width": "^4.1.0",
|
||||
"figures": "^3.2.0",
|
||||
"mute-stream": "^1.0.0",
|
||||
"run-async": "^3.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/core/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/editor": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-1.2.13.tgz",
|
||||
"integrity": "sha512-gBxjqt0B9GLN0j6M/tkEcmcIvB2fo9Cw0f5NRqDTkYyB9AaCzj7qvgG0onQ3GVPbMyMbbP4tWYxrBOaOdKpzNA==",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^5.1.1",
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"chalk": "^4.1.2",
|
||||
"external-editor": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/editor/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/expand": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-1.1.14.tgz",
|
||||
"integrity": "sha512-yS6fJ8jZYAsxdxuw2c8XTFMTvMR1NxZAw3LxDaFnqh7BZ++wTQ6rSp/2gGJhMacdZ85osb+tHxjVgx7F+ilv5g==",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^5.1.1",
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"chalk": "^4.1.2",
|
||||
"figures": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/expand/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/input": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-1.2.14.tgz",
|
||||
"integrity": "sha512-tISLGpUKXixIQue7jypNEShrdzJoLvEvZOJ4QRsw5XTfrIYfoWFqAjMQLerGs9CzR86yAI89JR6snHmKwnNddw==",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^5.1.1",
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"chalk": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/input/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/password": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-1.1.14.tgz",
|
||||
"integrity": "sha512-vL2BFxfMo8EvuGuZYlryiyAB3XsgtbxOcFs4H9WI9szAS/VZCAwdVqs8rqEeaAf/GV/eZOghIOYxvD91IsRWSg==",
|
||||
"dependencies": {
|
||||
"@inquirer/input": "^1.2.14",
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"ansi-escapes": "^4.3.2",
|
||||
"chalk": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/password/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/prompts": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-3.3.0.tgz",
|
||||
"integrity": "sha512-BBCqdSnhNs+WziSIo4f/RNDu6HAj4R/Q5nMgJb5MNPFX8sJGCvj9BoALdmR0HTWXyDS7TO8euKj6W6vtqCQG7A==",
|
||||
"dependencies": {
|
||||
"@inquirer/checkbox": "^1.5.0",
|
||||
"@inquirer/confirm": "^2.0.15",
|
||||
"@inquirer/core": "^5.1.1",
|
||||
"@inquirer/editor": "^1.2.13",
|
||||
"@inquirer/expand": "^1.1.14",
|
||||
"@inquirer/input": "^1.2.14",
|
||||
"@inquirer/password": "^1.1.14",
|
||||
"@inquirer/rawlist": "^1.2.14",
|
||||
"@inquirer/select": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/rawlist": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-1.2.14.tgz",
|
||||
"integrity": "sha512-xIYmDpYgfz2XGCKubSDLKEvadkIZAKbehHdWF082AyC2I4eHK44RUfXaoOAqnbqItZq4KHXS6jDJ78F2BmQvxg==",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^5.1.1",
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"chalk": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/rawlist/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/select": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-1.3.1.tgz",
|
||||
"integrity": "sha512-EgOPHv7XOHEqiBwBJTyiMg9r57ySyW4oyYCumGp+pGyOaXQaLb2kTnccWI6NFd9HSi5kDJhF7YjA+3RfMQJ2JQ==",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^5.1.1",
|
||||
"@inquirer/type": "^1.1.5",
|
||||
"ansi-escapes": "^4.3.2",
|
||||
"chalk": "^4.1.2",
|
||||
"figures": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/select/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/type": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.1.5.tgz",
|
||||
"integrity": "sha512-wmwHvHozpPo4IZkkNtbYenem/0wnfI6hvOcGKmPEa0DwuaH5XUQzFqy6OpEpjEegZMhYIk8HDYITI16BPLtrRA==",
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/inquirer": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.7.tgz",
|
||||
"integrity": "sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/through": "*",
|
||||
"rxjs": "^7.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mute-stream": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz",
|
||||
"integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz",
|
||||
"integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/through": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz",
|
||||
"integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/wrap-ansi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz",
|
||||
"integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
|
||||
"dependencies": {
|
||||
"type-fest": "^0.21.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chardet": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
|
||||
},
|
||||
"node_modules/cli-spinners": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
|
||||
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-width": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
|
||||
"integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/external-editor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
|
||||
"dependencies": {
|
||||
"chardet": "^0.7.0",
|
||||
"iconv-lite": "^0.4.24",
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^1.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mute-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==",
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/run-async": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
|
||||
"integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dependencies": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.21.3",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
|
||||
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "manager",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "npx tsc && node ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/inquirer": "^9.0.7",
|
||||
"@types/node": "^20.11.5",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inquirer/prompts": "^3.3.0",
|
||||
"chalk": "^3.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import chalk from "chalk";
|
||||
|
||||
export default class LoadSpinner {
|
||||
spinner: string[] = [
|
||||
'⠋',
|
||||
'⠙',
|
||||
'⠹',
|
||||
'⠸',
|
||||
'⠼',
|
||||
'⠴',
|
||||
'⠦',
|
||||
'⠧',
|
||||
'⠇',
|
||||
'⠏'
|
||||
];
|
||||
spinnerIndex: number;
|
||||
updateInterval: NodeJS.Timeout;
|
||||
message: string;
|
||||
|
||||
|
||||
constructor(message: string) {
|
||||
this.spinnerIndex = 0;
|
||||
this.message = message;
|
||||
|
||||
// The spinner is a loop that runs every 100ms
|
||||
// the spinner should look like this: [(spinner) message]
|
||||
|
||||
this.updateInterval = setInterval(() => {
|
||||
this.updateSpinner();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private updateSpinner() {
|
||||
process.stdout.write(`\r${this.spinner[this.spinnerIndex]} ${this.message}`);
|
||||
this.spinnerIndex++;
|
||||
if(this.spinnerIndex >= this.spinner.length) this.spinnerIndex = 0;
|
||||
}
|
||||
|
||||
changeMessage(message: string) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
removeSpinner(finishMessage?: string) {
|
||||
clearInterval(this.updateInterval);
|
||||
// replace the spinner with a tick but keep the message
|
||||
process.stdout.write(`\r${chalk.green("✓")} ${finishMessage ?? this.message}\n`);
|
||||
}
|
||||
|
||||
failSpinner(finishMessage?: string) {
|
||||
clearInterval(this.updateInterval);
|
||||
// replace the spinner with a cross but keep the message
|
||||
process.stdout.write(`\r${chalk.red("✗")} ${finishMessage ?? this.message}\n`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import { select, confirm } from '@inquirer/prompts';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
|
||||
|
||||
const configExists = fs.existsSync("../terratec.config.json");
|
||||
|
||||
|
||||
async function MainMenu() {
|
||||
if (!configExists) {
|
||||
console.log(chalk.red(`No terratec.config.json found`));
|
||||
|
||||
const action = await confirm({
|
||||
message: 'Do you want to create a new config file?',
|
||||
});
|
||||
|
||||
if (action) return await require('./modes/ConfigCreate').default();
|
||||
else process.exit(0);
|
||||
}
|
||||
|
||||
const action = await select({
|
||||
message: 'Select Terra manager action',
|
||||
choices: [
|
||||
{
|
||||
name: 'Actions',
|
||||
value: 'action',
|
||||
},
|
||||
{
|
||||
name: 'Edit config',
|
||||
value: 'edit',
|
||||
},
|
||||
{
|
||||
name: 'Exit',
|
||||
value: 'exit',
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
switch (action) {
|
||||
case 'action':
|
||||
{
|
||||
const selectedAction = await select({
|
||||
message: 'Select Terra manager action',
|
||||
choices: [
|
||||
{
|
||||
name: 'Start Dev Server',
|
||||
value: 'dev',
|
||||
},
|
||||
{
|
||||
name: 'Start Build',
|
||||
value: 'build',
|
||||
},
|
||||
{
|
||||
name: 'Exit',
|
||||
value: 'exit',
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
switch (selectedAction) {
|
||||
case 'dev':
|
||||
await require('./modes/Dev').default();
|
||||
break;
|
||||
case 'build':
|
||||
await require('./modes/Build').default();
|
||||
break;
|
||||
case 'exit':
|
||||
return MainMenu();
|
||||
default:
|
||||
console.log(chalk.red(`Unknown action: ${action}`));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'edit':
|
||||
await require('./modes/ConfigEdit').default();
|
||||
break;
|
||||
case 'exit':
|
||||
process.exit(0);
|
||||
break;
|
||||
default:
|
||||
console.log(chalk.red(`Unknown action: ${action}`));
|
||||
break;
|
||||
}
|
||||
|
||||
MainMenu();
|
||||
}
|
||||
|
||||
MainMenu();
|
|
@ -0,0 +1,43 @@
|
|||
import { input } from "@inquirer/prompts";
|
||||
import chalk from "chalk";
|
||||
import os from "os";
|
||||
import fs from "fs";
|
||||
import LoadSpinner from "../components/LoadSpinner";
|
||||
|
||||
export default async () => {
|
||||
const name = (await input({
|
||||
message: 'Project name',
|
||||
default: 'my-app',
|
||||
transformer: (input: string) => {
|
||||
return input.toLowerCase().replace(/ /g, '-');
|
||||
},
|
||||
validate: (input: string) => {
|
||||
if(input.length === 0) return 'Project name cannot be empty';
|
||||
return true;
|
||||
},
|
||||
|
||||
})).toLowerCase().replace(/ /g, '-');
|
||||
const description = await input({
|
||||
message: 'Project description',
|
||||
default: 'My awesome app',
|
||||
});
|
||||
const author = await input({
|
||||
message: 'Author',
|
||||
default: os.userInfo().username,
|
||||
});
|
||||
const version = "0.0.1";
|
||||
|
||||
const saveSpinner = new LoadSpinner("Saving config...");
|
||||
|
||||
const config: Terratec.Config = {
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
author,
|
||||
project: []
|
||||
};
|
||||
|
||||
fs.writeFileSync("../terratec.config.json", JSON.stringify(config, null, 2));
|
||||
|
||||
saveSpinner.removeSpinner("Saved config!");
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import { input, select } from "@inquirer/prompts";
|
||||
import chalk from "chalk";
|
||||
|
||||
export default async () => {
|
||||
const action = await select({
|
||||
message: 'Select Terra manager action',
|
||||
choices: [
|
||||
{
|
||||
name: 'Create new part',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Delete part',
|
||||
value: 'delete',
|
||||
},
|
||||
{
|
||||
name: 'Exit',
|
||||
value: 'exit',
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
switch (action) {
|
||||
case 'create':
|
||||
await require('./PartCreate').default();
|
||||
break;
|
||||
case 'delete':
|
||||
await require('./PartDelete').default();
|
||||
break;
|
||||
case 'exit':
|
||||
return;
|
||||
default:
|
||||
console.log(chalk.red(`Unknown action: ${action}`));
|
||||
break;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
import chalk from "chalk";
|
||||
import { exec } from "child_process";
|
||||
|
||||
export default async () => {
|
||||
console.log(chalk.green("Running dev mode"));
|
||||
|
||||
const frontend = exec("npm run start", {
|
||||
cwd: "../frontend",
|
||||
});
|
||||
|
||||
const backend = exec("npm run start", {
|
||||
cwd: "../backend",
|
||||
});
|
||||
|
||||
frontend.stdout?.on("data", (data) => {
|
||||
console.log(chalk.blueBright("[Frontend]"), data);
|
||||
});
|
||||
|
||||
backend.stdout?.on("data", (data) => {
|
||||
console.log(chalk.blueBright("[Backend]"), data);
|
||||
});
|
||||
|
||||
frontend.stderr?.on("data", (data) => {
|
||||
console.log(chalk.redBright("[Frontend]"), data);
|
||||
});
|
||||
|
||||
backend.stderr?.on("data", (data) => {
|
||||
console.log(chalk.redBright("[Backend]"), data);
|
||||
});
|
||||
};
|
|
@ -0,0 +1,76 @@
|
|||
import { input, select } from "@inquirer/prompts";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import LoadSpinner from "../components/LoadSpinner";
|
||||
|
||||
const configTxt = fs.readFileSync("../terratec.config.json").toString();
|
||||
const config: Terratec.Config = JSON.parse(configTxt);
|
||||
|
||||
const takenNames = config.project.map((project) => project.name);
|
||||
|
||||
export default async () => {
|
||||
console.log(chalk.green("Config generator"));
|
||||
|
||||
const type = await select({
|
||||
message: 'Part type',
|
||||
choices: [
|
||||
{
|
||||
name: 'Frontend',
|
||||
value: 'frontend',
|
||||
},
|
||||
{
|
||||
name: 'Backend',
|
||||
value: 'backend',
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
const name = (await input({
|
||||
message: 'Part name',
|
||||
transformer: (input: string) => {
|
||||
return input.toLowerCase().replace(/ /g, '-');
|
||||
},
|
||||
validate: (input: string) => {
|
||||
if (input.length === 0) return 'Part name cannot be empty';
|
||||
if (takenNames.includes(input)) return 'Part name already taken';
|
||||
return true;
|
||||
}
|
||||
})).toLowerCase().replace(/ /g, '-');
|
||||
const description = await input({
|
||||
message: 'Part description',
|
||||
default: 'My awesome part',
|
||||
});
|
||||
|
||||
const readTemplatesSpinner = new LoadSpinner("Reading templates...");
|
||||
|
||||
const templates = fs.readdirSync(`./dist/templates/${type}`);
|
||||
|
||||
readTemplatesSpinner.removeSpinner("Read templates!");
|
||||
|
||||
const template = await select({
|
||||
message: 'Part template',
|
||||
choices: [
|
||||
...templates.map((template) => ({
|
||||
name: template,
|
||||
value: template,
|
||||
})),
|
||||
],
|
||||
});
|
||||
|
||||
const saveSpinner = new LoadSpinner("Saving config...");
|
||||
|
||||
const config: Terratec.Config = JSON.parse(fs.readFileSync("../terratec.config.json").toString());
|
||||
|
||||
config.project.push({
|
||||
name,
|
||||
description,
|
||||
type: type as Terratec.ProjectType,
|
||||
template,
|
||||
});
|
||||
|
||||
fs.writeFileSync("../terratec.config.json", JSON.stringify(config, null, 2));
|
||||
|
||||
saveSpinner.removeSpinner("Saved config!");
|
||||
|
||||
await require(`../templates/${type}/${template}`).default(config.project[config.project.length - 1]);
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
import { input, select, confirm } from "@inquirer/prompts";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import LoadSpinner from "../components/LoadSpinner";
|
||||
|
||||
export default async () => {
|
||||
console.log(chalk.green("Config generator"));
|
||||
|
||||
const configTxT = fs.readFileSync("../terratec.config.json").toString();
|
||||
const config: Terratec.Config = JSON.parse(configTxT);
|
||||
|
||||
const part = await select({
|
||||
message: 'Delete part',
|
||||
choices: config.project.map((project) => (
|
||||
{
|
||||
name: project.name,
|
||||
value: project.name,
|
||||
}
|
||||
)),
|
||||
});
|
||||
|
||||
const sure = await confirm({
|
||||
message: `Are you sure you want to delete ${part}? All files will be deleted!`,
|
||||
default: false,
|
||||
});
|
||||
if (!sure) return console.log(chalk.red("Aborted"));
|
||||
|
||||
const saveSpinner = new LoadSpinner("Saving config...");
|
||||
|
||||
config.project = config.project.filter((project) => project.name !== part);
|
||||
|
||||
fs.writeFileSync("../terratec.config.json", JSON.stringify(config, null, 2));
|
||||
|
||||
saveSpinner.removeSpinner("Saved config!");
|
||||
|
||||
const deleteSpinner = new LoadSpinner("Removing files...");
|
||||
|
||||
fs.rmSync(`../${part}`, { recursive: true });
|
||||
|
||||
deleteSpinner.removeSpinner("Files removed!");
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
import { input, confirm, select, checkbox } from "@inquirer/prompts"
|
||||
import { exec } from "child_process"
|
||||
import LoadSpinner from "../../components/LoadSpinner"
|
||||
import chalk from "chalk"
|
||||
import fs from "fs"
|
||||
|
||||
export default async (props: Terratec.ProjectConfig) => {
|
||||
console.log(chalk.green("Running template..."))
|
||||
|
||||
const typescript = await confirm({
|
||||
message: "Use typescript?",
|
||||
default: true,
|
||||
});
|
||||
|
||||
const spinner = new LoadSpinner("Running template...");
|
||||
fs.mkdirSync(`../${props.name}`);
|
||||
|
||||
spinner.changeMessage("Setting up package...")
|
||||
|
||||
await new Promise((resolve) => {
|
||||
exec(`npm init -y`, {
|
||||
cwd: `../${props.name}`,
|
||||
}, () => {
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
|
||||
fs.mkdirSync(`../${props.name}/src`);
|
||||
|
||||
if(typescript) {
|
||||
spinner.changeMessage("Installing typescript...")
|
||||
await new Promise((resolve) => {
|
||||
exec(`npm i typescript @types/node @types/express --save-dev`, {
|
||||
cwd: `../${props.name}`,
|
||||
}, () => {
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
|
||||
spinner.changeMessage("Creating tsconfig.json...")
|
||||
|
||||
fs.writeFileSync(`../${props.name}/tsconfig.json`, JSON.stringify({
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}, null, 2));
|
||||
|
||||
fs.writeFileSync(`../${props.name}/src/index.ts`, `console.log("Hello world!")`);
|
||||
} else {
|
||||
fs.writeFileSync(`../${props.name}/src/index.js`, `console.log("Hello world!")`);
|
||||
}
|
||||
|
||||
spinner.changeMessage("Updating package.json...")
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync(`../${props.name}/package.json`).toString());
|
||||
packageJson.main = typescript ? "dist/index.js" : "src/index.js";
|
||||
packageJson.scripts.start = typescript ? "npx tsc && node ." : "node src/index.js";
|
||||
|
||||
fs.writeFileSync(`../${props.name}/package.json`, JSON.stringify(packageJson, null, 2));
|
||||
|
||||
spinner.changeMessage("Installing dependencies...")
|
||||
|
||||
await new Promise((resolve) => {
|
||||
exec(`npm i express`, {
|
||||
cwd: `../${props.name}`,
|
||||
}, () => {
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
|
||||
spinner.removeSpinner("Template execution finished!");
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
import { input, confirm, select, checkbox } from "@inquirer/prompts"
|
||||
import { exec } from "child_process"
|
||||
import LoadSpinner from "../../components/LoadSpinner"
|
||||
import chalk from "chalk"
|
||||
|
||||
export default async (props: Terratec.ProjectConfig) => {
|
||||
console.log(chalk.green("Running template..."))
|
||||
|
||||
const typescript = await confirm({
|
||||
message: "Use typescript?",
|
||||
default: true,
|
||||
});
|
||||
|
||||
const spinner = new LoadSpinner("Running template...");
|
||||
spinner.changeMessage("Running create-react-app...")
|
||||
|
||||
await new Promise((resolve) => {
|
||||
exec(`npx create-react-app ${props.name}${typescript && " --template typescript"}`, {
|
||||
cwd: "..",
|
||||
}, () => {
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
|
||||
spinner.removeSpinner("Template execution finished!");
|
||||
|
||||
const optionDependencies = await confirm({
|
||||
message: "Do you want to install optional dependencies?",
|
||||
default: true,
|
||||
});
|
||||
|
||||
if(optionDependencies) {
|
||||
const dependencies = await checkbox({
|
||||
message: "Select dependencies",
|
||||
choices: [
|
||||
{
|
||||
name: "react-router-dom",
|
||||
value: "react-router-dom",
|
||||
},
|
||||
{
|
||||
name: "axios",
|
||||
value: "axios",
|
||||
},
|
||||
{
|
||||
name: "react-query",
|
||||
value: "react-query",
|
||||
},
|
||||
{
|
||||
name: "Material UI",
|
||||
value: "@mui/icons-material @mui/material @emotion/styled @emotion/react",
|
||||
},
|
||||
{
|
||||
name: "Zustand",
|
||||
value: "zustand",
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const dependenciesSpinner = new LoadSpinner("Installing dependencies...");
|
||||
|
||||
await new Promise((resolve) => {
|
||||
exec(`npm i ${dependencies.join(" ")}`, {
|
||||
cwd: `../${props.name}`,
|
||||
}, () => {
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
|
||||
dependenciesSpinner.removeSpinner("Dependencies installed!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
declare namespace Terratec {
|
||||
type ProjectType = 'frontend' | 'backend';
|
||||
|
||||
interface Config {
|
||||
name: string;
|
||||
description: string;
|
||||
version: string;
|
||||
author: string;
|
||||
project: ProjectConfig[];
|
||||
}
|
||||
|
||||
interface ProjectConfig {
|
||||
name: string;
|
||||
description: string;
|
||||
type: ProjectType;
|
||||
template: string;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "NodeNext", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "my-app",
|
||||
"description": "My awesome app",
|
||||
"version": "0.0.1",
|
||||
"author": "niko",
|
||||
"project": []
|
||||
}
|