Compare commits
No commits in common. 'master' and 'submitter-web' have entirely different histories.
master
...
submitter-
@ -1,38 +0,0 @@
|
||||
module.exports = {
|
||||
env: { node: true },
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint", "prettier", "jsdoc"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
"plugin:prettier/recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
},
|
||||
project: "./tsconfig.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/require-await": 0,
|
||||
"jsdoc/require-description": 2,
|
||||
"no-await-in-loop": 2,
|
||||
},
|
||||
settings: { jsdoc: { mode: "typescript" } },
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.subtest.ts", "*.test.ts"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-unsafe-member-access": 0,
|
||||
"prefer-const": 0,
|
||||
"@typescript-eslint/no-unsafe-call": 0,
|
||||
"@typescript-eslint/no-unsafe-return": 0,
|
||||
"@typescript-eslint/no-unsafe-assignment": 0,
|
||||
"no-await-in-loop": 1, // sometimes it's easier to debug when requests run sequentially
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@ -1,11 +1,3 @@
|
||||
node_modules
|
||||
.node_repl_history
|
||||
.config
|
||||
.npm
|
||||
.idea
|
||||
.DS_Store
|
||||
lib
|
||||
dist
|
||||
@types
|
||||
.cache
|
||||
public
|
||||
/.cache/
|
||||
/dist/
|
||||
/node_modules/
|
||||
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "es5",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.yml",
|
||||
"options": {
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
db:
|
||||
image: mongo:4.4-bionic
|
||||
ports:
|
||||
- "127.0.0.1:20724:27017"
|
||||
mailcatcher:
|
||||
image: schickling/mailcatcher:latest
|
||||
ports:
|
||||
- "127.0.0.1:1080:1080"
|
||||
- "127.0.0.1:1025:1025"
|
File diff suppressed because it is too large
Load Diff
@ -1,50 +1,18 @@
|
||||
{
|
||||
"name": "zglaszansko-web",
|
||||
"version": "0.1.0",
|
||||
"description": "Web aplikacja ułatwiająca wysyłanie zgłoszeń do Poznańskiej Straży Miejskiej",
|
||||
"main": "./dist/back/index.js",
|
||||
"scripts": {
|
||||
"back:build": "tsc",
|
||||
"back:watch": "npm run back:build -- --watch",
|
||||
"front:build": "parcel build --out-dir public src/front/*.html",
|
||||
"front:watch": "parcel watch --out-dir public src/front/*.html",
|
||||
"start": "nodemon lib/back/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "gitea@git.kuba-orlik.name:kuba/zglaszansko-web.git"
|
||||
},
|
||||
"author": "foki",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@koa/router": "^10.0.0",
|
||||
"@types/koa": "^2.11.6",
|
||||
"@types/koa-mount": "^4.0.0",
|
||||
"@types/koa-static": "^4.0.1",
|
||||
"@types/koa__router": "^8.0.3",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"axios": "^0.21.1",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"koa": "^2.13.0",
|
||||
"koa-mount": "^4.0.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"leaflet": "^1.7.1",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-leaflet": "^3.0.5",
|
||||
"sealious": "^0.13.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/leaflet": "^1.5.19",
|
||||
"@types/node": "^14.14.20",
|
||||
"@types/request": "^2.48.5",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-jsdoc": "^30.0.3",
|
||||
"prettier": "^2.0.5",
|
||||
"sass": "^1.32.0",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
"name": "zglaszansko-web",
|
||||
"version": "0.1.0",
|
||||
"description": "Web aplikacja ułatwiająca wysyłanie zgłoszeń do Poznańskiej Straży Miejskiej",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "parcel src/index.html"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "gitea@git.kuba-orlik.name:kuba/zglaszansko-web.git"
|
||||
},
|
||||
"author": "foki",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"parcel-bundler": "^1.12.4"
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 328 B |
@ -1,51 +0,0 @@
|
||||
import Koa from "koa";
|
||||
import _locreq from "locreq";
|
||||
import { resolve } from "path";
|
||||
import Static from "koa-static";
|
||||
import Router from "@koa/router";
|
||||
import mount from "koa-mount";
|
||||
const locreq = _locreq(__dirname);
|
||||
import Sealious, { App } from "sealious";
|
||||
|
||||
declare module "koa" {
|
||||
interface BaseContext {
|
||||
$context: Sealious.Context;
|
||||
$app: Sealious.App;
|
||||
$body: Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
const app = new (class extends App {
|
||||
config = {
|
||||
upload_path: locreq.resolve("uploaded_files"),
|
||||
datastore_mongo: {
|
||||
host: "localhost",
|
||||
port: 20724,
|
||||
db_name: "zglaszansko-web",
|
||||
},
|
||||
email: {
|
||||
from_address: "sealious-playground@example.com",
|
||||
from_name: "Sealious playground app",
|
||||
},
|
||||
};
|
||||
|
||||
manifest = {
|
||||
name: "Sealious Playground",
|
||||
logo: resolve(__dirname, "../assets/logo.png"),
|
||||
version: "0.0.1",
|
||||
default_language: "en",
|
||||
base_url: "http://localhost:8080",
|
||||
admin_email: "admin@example.com",
|
||||
colors: {
|
||||
primary: "#5294a1",
|
||||
},
|
||||
};
|
||||
collections = {
|
||||
...App.BaseCollections,
|
||||
};
|
||||
})();
|
||||
|
||||
const port = 8080;
|
||||
|
||||
console.log(`Listening on 127.0.0.1:${port}`);
|
||||
app.start();
|
||||
app.HTTPServer.addStaticRoute("/", locreq.resolve("public"));
|
@ -1,52 +0,0 @@
|
||||
import axios from "axios";
|
||||
import React, { ReactElement } from "react";
|
||||
import "./styles/nav.scss";
|
||||
interface Props {
|
||||
isLogged: boolean;
|
||||
logoutState: () => void;
|
||||
}
|
||||
const NOT_LOGGED = [
|
||||
{ name: "aplikacja", href: "/" },
|
||||
{ name: "logowanie", href: "login.html" },
|
||||
{ name: "rejestracja", href: "register.html" },
|
||||
];
|
||||
|
||||
const LOGGED = [{ name: "aplikacja", href: "/" }];
|
||||
|
||||
function Nav({ isLogged, logoutState }: Props): ReactElement {
|
||||
const logout = async () => {
|
||||
await axios.delete(
|
||||
"http://localhost:8080/api/v1/collections/sessions/current"
|
||||
);
|
||||
logoutState();
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="navbar">
|
||||
<h2 className="navbar__title">Zgłaszańsko web</h2>
|
||||
<div className="navbar__links">
|
||||
{isLogged
|
||||
? LOGGED.map((ele) => (
|
||||
<>
|
||||
<a href={ele.href} className="button-link">
|
||||
{ele.name}
|
||||
</a>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="button-link"
|
||||
>
|
||||
Wyloguj
|
||||
</button>
|
||||
</>
|
||||
))
|
||||
: NOT_LOGGED.map((ele) => (
|
||||
<a href={ele.href} className="button-link">
|
||||
{ele.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export default Nav;
|
@ -1,347 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import React, {
|
||||
useState,
|
||||
useReducer,
|
||||
MouseEvent,
|
||||
FC,
|
||||
ChangeEvent,
|
||||
FormEvent,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { render } from "react-dom";
|
||||
import { offenses } from "./offenses";
|
||||
import axios from "axios";
|
||||
import Nav from "./Nav";
|
||||
//Importing fileReducer to use in useReducer hook, and File interface to use in looping over array of files
|
||||
import { fileReducer, filesInitialState, File } from "./fileReducer";
|
||||
import { formReducer, fromInitialState } from "./formReducer";
|
||||
import "regenerator-runtime/runtime";
|
||||
import { MapContainer, TileLayer, Marker, useMapEvents } from "react-leaflet";
|
||||
import "./styles/reset.css";
|
||||
import "./styles/app.scss";
|
||||
import { getAddress } from "./location";
|
||||
import { FileType } from "./file";
|
||||
const App: FC = () => {
|
||||
const [isLogged, setIsLogged] = useState(false);
|
||||
const [fileState, fileDispatch] = useReducer(
|
||||
fileReducer,
|
||||
filesInitialState
|
||||
);
|
||||
const [formState, formDispatch] = useReducer(formReducer, fromInitialState);
|
||||
const [mapPin, setMapPin] = useState({ lat: 0, lon: 0 });
|
||||
|
||||
const checkUser = async (): Promise<void> => {
|
||||
try {
|
||||
await axios.get(
|
||||
"http://localhost:8080/api/v1/collections/users/me"
|
||||
);
|
||||
setIsLogged(true);
|
||||
} catch (error) {
|
||||
setIsLogged(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void checkUser();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void getAddress(mapPin).then((blob) =>
|
||||
formDispatch({
|
||||
type: "CHANGE_FIELD",
|
||||
payload: {
|
||||
field: "address",
|
||||
value: blob,
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [mapPin]);
|
||||
|
||||
const handleSubmit = async (
|
||||
event: FormEvent<HTMLFormElement> | MouseEvent<HTMLButtonElement>
|
||||
): Promise<void> => {
|
||||
event.preventDefault();
|
||||
console.log(fileState, formState);
|
||||
};
|
||||
|
||||
const handleUpload = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
const readAndPreview = (file: FileType) => {
|
||||
console.log(file);
|
||||
// Make sure `file.name` matches our extensions criteria
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (/\.(jpe?g|png|svg)$/i.test(file.name)) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener(
|
||||
"load",
|
||||
function () {
|
||||
fileDispatch({
|
||||
type: "ADD_FILE",
|
||||
payload: {
|
||||
img: this.result,
|
||||
id: Date.now().toString(),
|
||||
selected: false,
|
||||
},
|
||||
});
|
||||
},
|
||||
false
|
||||
);
|
||||
reader.readAsDataURL((file as unknown) as Blob);
|
||||
}
|
||||
};
|
||||
if (files) {
|
||||
[].forEach.call(files, readAndPreview);
|
||||
}
|
||||
};
|
||||
|
||||
const MapComponent = () => {
|
||||
useMapEvents({
|
||||
click: async (e) => {
|
||||
setMapPin({ lat: e.latlng.lat, lon: e.latlng.lng });
|
||||
},
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
const formMessage = `Rejestracja: ${formState.plate}\nMiejsce: ${
|
||||
formState.address.road === undefined ? "" : formState.address.road
|
||||
} ${
|
||||
formState.address.house_number === undefined
|
||||
? ""
|
||||
: formState.address.house_number
|
||||
}\nData: ${new Date().toLocaleString()}\nMoje dane: ${
|
||||
formState.name + ","
|
||||
} ${formState.email}\n${
|
||||
formState.my_address
|
||||
}\nPowód: ${formState.offenses.join(", \n")}\nKomentarz: ${
|
||||
formState.comment
|
||||
}
|
||||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav isLogged={isLogged} logoutState={() => setIsLogged(false)} />
|
||||
<div className="container">
|
||||
<section className="container__section">
|
||||
<h2 className="container__section__title">Zdjęcia</h2>
|
||||
<div className="container__section__photos">
|
||||
{fileState.files.map((file: File, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={(e: MouseEvent) =>
|
||||
fileDispatch({
|
||||
type: "SELECT_FILE",
|
||||
payload: {
|
||||
id: (e.target as Element).id,
|
||||
},
|
||||
})
|
||||
}
|
||||
className="section__photos__item"
|
||||
>
|
||||
<img
|
||||
src={file.img as string}
|
||||
id={file.id}
|
||||
className={
|
||||
file.selected ? "img--active" : "img"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
name="image-upload"
|
||||
id="input"
|
||||
accept="image/*"
|
||||
className="input"
|
||||
multiple
|
||||
onChange={(e) => handleUpload(e)}
|
||||
/>
|
||||
</section>
|
||||
<section className="container__section">
|
||||
<h2 className="container__section__title">Mapa</h2>
|
||||
<div>
|
||||
<MapContainer
|
||||
center={[52.39663, 16.89866]}
|
||||
className="container__section__map"
|
||||
zoom={16}
|
||||
scrollWheelZoom={true}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
{mapPin.lat !== 0 ? (
|
||||
<Marker
|
||||
position={[mapPin.lat, mapPin.lon]}
|
||||
></Marker>
|
||||
) : null}
|
||||
<MapComponent />
|
||||
</MapContainer>
|
||||
</div>
|
||||
<p>
|
||||
Wybrany adres:{" "}
|
||||
{formState.address.road === undefined
|
||||
? ""
|
||||
: formState.address.road}{" "}
|
||||
{formState.address.house_number === undefined
|
||||
? ""
|
||||
: formState.address.house_number}
|
||||
</p>
|
||||
</section>
|
||||
<section className="container__section">
|
||||
<h2 className="container__section__title">
|
||||
Formularz zgłoszeniowy
|
||||
</h2>
|
||||
<form
|
||||
className="container__section__form"
|
||||
onSubmit={(e) => handleSubmit(e)}
|
||||
>
|
||||
<label htmlFor="name">Twoje imię i nazwisko</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
id="name"
|
||||
value={formState.name}
|
||||
onChange={(e) =>
|
||||
formDispatch({
|
||||
type: "CHANGE_FIELD",
|
||||
payload: {
|
||||
field: e.target.id,
|
||||
value: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label htmlFor="email">Twój adres email</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
id="email"
|
||||
value={formState.email}
|
||||
onChange={(e) =>
|
||||
formDispatch({
|
||||
type: "CHANGE_FIELD",
|
||||
payload: {
|
||||
field: e.target.id,
|
||||
value: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label htmlFor="my_address">
|
||||
Twój adres zamieszkania
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
id="my_address"
|
||||
value={formState.my_address}
|
||||
onChange={(e) =>
|
||||
formDispatch({
|
||||
type: "CHANGE_FIELD",
|
||||
payload: {
|
||||
field: e.target.id,
|
||||
value: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label htmlFor="plate">
|
||||
Numer tablicy rejestracyjnej
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
id="plate"
|
||||
value={formState.plate}
|
||||
onChange={(e) =>
|
||||
formDispatch({
|
||||
type: "CHANGE_FIELD",
|
||||
payload: {
|
||||
field: e.target.id,
|
||||
value: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div className="container__section__form__offenses">
|
||||
{offenses.map((offence, index) => (
|
||||
<div key={index}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={offence.id}
|
||||
value={offence.car_is}
|
||||
name={offence.car_is}
|
||||
className="chceckbox"
|
||||
onChange={(e) =>
|
||||
e.target.checked === true
|
||||
? formDispatch({
|
||||
type: "ADD_OFFENCE",
|
||||
payload: {
|
||||
value:
|
||||
e.target.value,
|
||||
field: "checkbox",
|
||||
},
|
||||
})
|
||||
: formDispatch({
|
||||
type: "DELETE_OFFENCE",
|
||||
payload: {
|
||||
value:
|
||||
e.target.value,
|
||||
field: "checkbox",
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor={offence.id}
|
||||
className="label"
|
||||
>
|
||||
{offence.name}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<label htmlFor="comment">
|
||||
Twój komentarz do zgłoszenia
|
||||
</label>
|
||||
<textarea
|
||||
className="textarea textarea--small"
|
||||
id="comment"
|
||||
name="comment"
|
||||
value={formState.comment}
|
||||
onChange={(e) =>
|
||||
formDispatch({
|
||||
type: "CHANGE_FIELD",
|
||||
payload: {
|
||||
field: e.target.id,
|
||||
value: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label htmlFor="message">
|
||||
Wiadomość dla straży miejskiej
|
||||
</label>
|
||||
<textarea
|
||||
className="textarea"
|
||||
id="message"
|
||||
disabled={true}
|
||||
value={formMessage}
|
||||
/>
|
||||
|
||||
<button
|
||||
className="button"
|
||||
onClick={(e) => handleSubmit(e)}
|
||||
>
|
||||
Wysłanie zgłoszenia
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, document.getElementById("root"));
|
@ -1,7 +0,0 @@
|
||||
export interface FileType {
|
||||
lastModified: number;
|
||||
name: string;
|
||||
size: number;
|
||||
type: string;
|
||||
webkitRelativePath: string;
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
export interface File {
|
||||
img?: unknown;
|
||||
id: string;
|
||||
selected?: boolean;
|
||||
}
|
||||
interface FilesState {
|
||||
files: Array<File>;
|
||||
}
|
||||
interface FilesAction {
|
||||
type: string;
|
||||
payload: File;
|
||||
}
|
||||
export const filesInitialState = {
|
||||
files: [],
|
||||
};
|
||||
export function fileReducer(
|
||||
state: FilesState,
|
||||
action: FilesAction
|
||||
): FilesState {
|
||||
switch (action.type) {
|
||||
case "ADD_FILE":
|
||||
return {
|
||||
...state,
|
||||
files: [
|
||||
...state.files,
|
||||
{
|
||||
img: action.payload.img,
|
||||
id: action.payload.id,
|
||||
selected: action.payload.selected,
|
||||
},
|
||||
],
|
||||
};
|
||||
case "SELECT_FILE":
|
||||
return {
|
||||
...state,
|
||||
files: state.files.map((file: File) =>
|
||||
file.id == action.payload.id
|
||||
? { ...file, selected: true }
|
||||
: { ...file, selected: false }
|
||||
),
|
||||
};
|
||||
case "CLEAR_FILES":
|
||||
return {
|
||||
files: [],
|
||||
};
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
import { Address } from "./location";
|
||||
interface FormState {
|
||||
name: string;
|
||||
email: string;
|
||||
plate: string;
|
||||
offenses: (Address | void | string)[];
|
||||
comment: string;
|
||||
my_address: string;
|
||||
address: Address;
|
||||
}
|
||||
interface FormAction {
|
||||
type: string;
|
||||
payload: {
|
||||
field: string;
|
||||
value: Address | void | string;
|
||||
};
|
||||
}
|
||||
export const fromInitialState = {
|
||||
name: "",
|
||||
email: "",
|
||||
plate: "",
|
||||
my_address: "",
|
||||
offenses: [],
|
||||
comment: "",
|
||||
address: {
|
||||
house_number: "",
|
||||
road: "",
|
||||
suburb: "",
|
||||
neighbourhood: "",
|
||||
hamlet: "",
|
||||
},
|
||||
};
|
||||
export function formReducer(state: FormState, action: FormAction): FormState {
|
||||
switch (action.type) {
|
||||
case "CHANGE_FIELD":
|
||||
return {
|
||||
...state,
|
||||
//action.payload.field is equal to name of the imput, example: e.target.name = plate
|
||||
[action.payload.field]: action.payload.value,
|
||||
};
|
||||
case "ADD_OFFENCE":
|
||||
return {
|
||||
...state,
|
||||
offenses: [...state.offenses, action.payload.value],
|
||||
};
|
||||
case "DELETE_OFFENCE":
|
||||
return {
|
||||
...state,
|
||||
offenses: state.offenses.filter(
|
||||
(offence: string) => offence !== action.payload.value
|
||||
),
|
||||
};
|
||||
case "CHANGE_ADDRESS":
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Zgłaszańsko</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,24 +0,0 @@
|
||||
import axios from "axios";
|
||||
export type Location = {
|
||||
lat: number;
|
||||
lon: number;
|
||||
};
|
||||
|
||||
export type Address = {
|
||||
house_number?: string;
|
||||
road?: string;
|
||||
suburb?: string;
|
||||
neighbourhood?: string;
|
||||
hamlet?: string;
|
||||
};
|
||||
|
||||
export async function getAddress(location: Location): Promise<Address | void> {
|
||||
const nominatim_url = `https://nominatim.openstreetmap.org/reverse?lat=${location.lat}&lon=${location.lon}&format=jsonv2`;
|
||||
try {
|
||||
const data = await axios.get(nominatim_url);
|
||||
const nominatim_dat = data.data as { address: Address };
|
||||
return nominatim_dat.address;
|
||||
} catch (error) {
|
||||
console.log(123);
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Zgłaszańsko</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./login.tsx"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,94 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import React, { ReactElement, useEffect, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import Nav from "./Nav";
|
||||
import "./styles/reset.css";
|
||||
import "./styles/app.scss";
|
||||
import "regenerator-runtime/runtime";
|
||||
import axios from "axios";
|
||||
|
||||
export default function Login(): ReactElement {
|
||||
const [isLogged, setIsLogged] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [loginState, setLoginState] = useState({
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
const checkUser = async (): Promise<void> => {
|
||||
try {
|
||||
await axios.get(
|
||||
"http://localhost:8080/api/v1/collections/users/me"
|
||||
);
|
||||
setIsLogged(true);
|
||||
} catch (error) {
|
||||
setIsLogged(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
void checkUser();
|
||||
}, []);
|
||||
|
||||
const postLogin = async () => {
|
||||
try {
|
||||
await axios.post("http://localhost:8080/api/v1/sessions/", {
|
||||
username: loginState.username,
|
||||
password: loginState.password,
|
||||
});
|
||||
void checkUser();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const notLoggedForm = () => {
|
||||
return (
|
||||
<form>
|
||||
<label htmlFor="username">
|
||||
Nazwa użytkownika
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
value={loginState.username}
|
||||
onChange={(e) =>
|
||||
setLoginState({
|
||||
...loginState,
|
||||
username: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="password">
|
||||
Hasło
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={loginState.password}
|
||||
onChange={(e) =>
|
||||
setLoginState({
|
||||
...loginState,
|
||||
password: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
console.log(postLogin());
|
||||
}}
|
||||
>
|
||||
Zaloguj
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Nav isLogged={isLogged} logoutState={() => setIsLogged(false)} />
|
||||
{isLogged ? <h1>Jesteś zalogowany</h1> : notLoggedForm()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
ReactDOM.render(<Login />, document.getElementById("root"));
|
@ -1,110 +0,0 @@
|
||||
export type Offense = {
|
||||
name: string;
|
||||
car_is: string;
|
||||
id: string;
|
||||
implies?: string[];
|
||||
};
|
||||
|
||||
export const offenses: Offense[] = [
|
||||
{
|
||||
id: "obszar_wyłączony",
|
||||
name: "Parkowanie na obszarze wyłączonym z ruchu",
|
||||
car_is: "jest zaparkowany na obszarze wyłączonym z ruchu",
|
||||
},
|
||||
{
|
||||
id: "widoczność_pasy",
|
||||
name: "Ograniczanie widoczności na pasach",
|
||||
car_is:
|
||||
"ogranicza widoczność na przejściu dla pieszych, powodując zagrożenie osób korzystających z przejścia",
|
||||
},
|
||||
{
|
||||
id: "widoczność_skrz",
|
||||
name: "Ograniczanie widoczności na skrzyżowaniu",
|
||||
car_is:
|
||||
"ogranicza widoczność na skrzyżowaniu, powodując zagrożenie dla uczestników ruchu",
|
||||
},
|
||||
{
|
||||
id: "utrudnia_ruch_rowerom",
|
||||
name: "Utrudnianie ruchu rowerowego",
|
||||
car_is: "utrudnia ruch rowerowy",
|
||||
},
|
||||
{
|
||||
id: "utrudnia_ruch_pieszym",
|
||||
name: "Utrudnianie ruchu pieszego",
|
||||
car_is: "utrudnia ruch pieszych",
|
||||
},
|
||||
{
|
||||
id: "poza_wyzn",
|
||||
name: "Parkowanie poza wyznaczonym miejscem",
|
||||
car_is: "jest zaparkowany poza wyznaczonym miejscem parkingowym",
|
||||
},
|
||||
{
|
||||
id: "przed_przejsc",
|
||||
name: "Parkowanie <10m przed przejściem dla pieszych",
|
||||
car_is: "jest zaparkowany mniej niż 10m przed przejściem dla pieszych",
|
||||
implies: ["widoczność_pasy"],
|
||||
},
|
||||
{
|
||||
id: "na_zakazie",
|
||||
name: "Parkowanie za znakiem zakazu parkowania",
|
||||
car_is: "jest zaparkowany za znakiem zakazu parkowania",
|
||||
},
|
||||
{
|
||||
id: "brama",
|
||||
name: "Blokowanie bramy wjazdowej",
|
||||
car_is: "blokuje bramę wjazdową",
|
||||
},
|
||||
{
|
||||
id: "na_chodzie",
|
||||
name: "Postój na chodzie",
|
||||
car_is: "ma włączony silnik podczas postoju",
|
||||
},
|
||||
{
|
||||
id: "blk_chodnik",
|
||||
name: "Blokowanie chodnika",
|
||||
car_is:
|
||||
"jest zaparkowany na chodniku pozostawiając mniej niż 1,5m dla pieszych",
|
||||
implies: ["utrudnia_ruch_pieszym"],
|
||||
},
|
||||
{
|
||||
id: "chyba2.5t",
|
||||
name: "Duży samochód dostawczy na chodniku",
|
||||
car_is:
|
||||
"najprawdopodobniej przekracza dopuszczalną całkowitą masę 2,5t będąc zaparkowanym na chodniku",
|
||||
},
|
||||
{
|
||||
id: "hydrant",
|
||||
name: "Blokowanie hydrantu",
|
||||
car_is: "blokuje dostęp do hydrantu",
|
||||
},
|
||||
{
|
||||
id: "przystanek_15",
|
||||
name: "Parkowanie <15m od przystanku",
|
||||
car_is:
|
||||
"jest zaparkowany w odległości mniejszej niż 15m od tablicy oznaczającej przystanek",
|
||||
},
|
||||
{
|
||||
id: "utr_innym_zapark",
|
||||
name: "Utrudnianie wyjazdu innym zaparkowanym samochodom",
|
||||
car_is:
|
||||
"dokonuje postoju w miejscu utrudniającym dostęp do innych, prawidłowo zaparkowanych pojazdów lub wyjazd tych pojazdów",
|
||||
},
|
||||
{
|
||||
id: "pas_rowerow",
|
||||
name: "blokuje pas rowerów",
|
||||
car_is: "zaparkowany na pasie dla rowerów",
|
||||
implies: ["utrudnia_ruch_rowerom"],
|
||||
},
|
||||
{
|
||||
id: "t30",
|
||||
name: "Parkowanie niezgodnie z rysunkiem na tabliczce (T-30)",
|
||||
car_is:
|
||||
"nie stosuje się do znaku T-30, wskazującego sposób ustawienia pojazdu względem krawędzi jezdni",
|
||||
},
|
||||
{
|
||||
id: "blokuje_znak",
|
||||
name: "Zasłanianie znaku drogowego",
|
||||
car_is:
|
||||
"zaparkowany w odległości mniejszej niż 10m od przedniej strony znaku drogowego, zasłaniając go (Art 49, punkt 1, ust. 6)",
|
||||
},
|
||||
];
|
@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Zgłaszańsko</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./register.tsx"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,78 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import React, { ReactElement, useEffect, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import Nav from "./Nav";
|
||||
import "./styles/reset.css";
|
||||
import "./styles/app.scss";
|
||||
import "regenerator-runtime/runtime";
|
||||
import axios from "axios";
|
||||
|
||||
export default function Register(): ReactElement {
|
||||
const [isLogged, setIsLogged] = useState(false);
|
||||
const [registerState, setRegisterState] = useState({
|
||||
email: "",
|
||||
});
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const checkUser = async (): Promise<void> => {
|
||||
try {
|
||||
await axios.get(
|
||||
"http://localhost:8080/api/v1/collections/users/me"
|
||||
);
|
||||
setIsLogged(true);
|
||||
} catch (error) {
|
||||
setIsLogged(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
void checkUser();
|
||||
}, []);
|
||||
const postRegister = async () => {
|
||||
try {
|
||||
await axios.post(
|
||||
"http://localhost:8080/api/v1/collections/registration-intents",
|
||||
{
|
||||
email: registerState.email,
|
||||
}
|
||||
);
|
||||
void checkUser();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
const notLoggedForm = () => {
|
||||
return (
|
||||
<form>
|
||||
<label htmlFor="email">
|
||||
Adres email
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={registerState.email}
|
||||
onChange={(e) =>
|
||||
setRegisterState({
|
||||
email: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
console.log(postRegister());
|
||||
}}
|
||||
>
|
||||
Zaloguj
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Nav isLogged={isLogged} logoutState={() => setIsLogged(false)} />
|
||||
{isLogged ? <h1>Jesteś zalogowany</h1> : notLoggedForm()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
ReactDOM.render(<Register />, document.getElementById("root"));
|
@ -1,147 +0,0 @@
|
||||
.button:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
}
|
||||
.button {
|
||||
background-color: #999;
|
||||
height: 2rem;
|
||||
line-height: 2rem;
|
||||
padding: 0 0.75rem;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin: 0 15px;
|
||||
}
|
||||
.container__section {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
display: block;
|
||||
height: auto;
|
||||
}
|
||||
.container__section__title {
|
||||
margin: 15px 0;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.container__section__photos {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
row-gap: 35px;
|
||||
column-gap: 15px;
|
||||
justify-items: stretch;
|
||||
}
|
||||
.section__photos__item {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.section__photos__item--active {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.container__section__map {
|
||||
cursor: default;
|
||||
height: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
.container__section__form {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.textarea {
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
font-size: 1.1rem;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
.textarea--small {
|
||||
height: 150px;
|
||||
}
|
||||
.label {
|
||||
width: 100%;
|
||||
}
|
||||
.container__section__form__offenses {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
//to change name
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.img--active {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 3px solid seagreen;
|
||||
}
|
||||
.chceckbox {
|
||||
appearance: none;
|
||||
vertical-align: middle;
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
background: white;
|
||||
border: 0.1em solid #000000;
|
||||
position: relative;
|
||||
}
|
||||
input[type="checkbox"]:checked {
|
||||
background: #000000;
|
||||
}
|
||||
/*breakpoint for mobile*/
|
||||
@media (max-width: 1200px) {
|
||||
body {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.container {
|
||||
flex-direction: column;
|
||||
}
|
||||
.container__section__title {
|
||||
margin: 25px 0;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.container__section__photos {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
}
|
||||
.section__photos__item {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
.container__section__form {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
}
|
||||
.textarea {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.input {
|
||||
width: 90%;
|
||||
height: 75px;
|
||||
font-size: 2rem;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.input--textarea {
|
||||
height: 450px;
|
||||
width: 90%;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
.navbar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: #000000 2px solid;
|
||||
padding: 15px 0;
|
||||
}
|
||||
.navbar__title {
|
||||
font-size: 1.5em;
|
||||
margin: 0 15px;
|
||||
}
|
||||
.navbar__links {
|
||||
display: flex;
|
||||
}
|
||||
.button-link {
|
||||
background-color: #999;
|
||||
height: 2rem;
|
||||
line-height: 2rem;
|
||||
padding: 0 0.75rem;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin: 0 15px;
|
||||
font-size: 0.9em;
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
b,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: "";
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Zgłaszańsko</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Zgłaszańsko</h1>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1 @@
|
||||
import { submit } from "poznan-sm-submit";
|
@ -1,24 +0,0 @@
|
||||
FROM node:15-alpine
|
||||
LABEL maintainer="Jakub Pieńkowski <jakski@sealcode.org>"
|
||||
|
||||
ENV UID=node \
|
||||
GID=node \
|
||||
HOME=/opt/sealious
|
||||
|
||||
RUN sed -i 's/http\:\/\/dl-cdn.alpinelinux.org/https\:\/\/mirrors.dotsrc.org/g' /etc/apk/repositories
|
||||
# Tini will ensure that any orphaned processes get reaped properly.
|
||||
RUN apk add --no-cache tini
|
||||
RUN apk --update add git
|
||||
RUN apk --update add python
|
||||
RUN apk --update add make
|
||||
RUN apk --update add g++
|
||||
|
||||
VOLUME $HOME
|
||||
WORKDIR $HOME
|
||||
|
||||
USER $UID:$GID
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["/usr/local/bin/node", "."]
|
@ -1,20 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "ES6",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["dom"],
|
||||
"outDir": "lib",
|
||||
"checkJs": true,
|
||||
"allowJs": true,
|
||||
"declarationDir": "@types",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "ES2020",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["es6", "esnext"],
|
||||
"outDir": "lib",
|
||||
"checkJs": true,
|
||||
"allowJs": true,
|
||||
"declarationDir": "@types",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue