better review design, caddy now listens on localhost too
This commit is contained in:
@@ -9,3 +9,15 @@ api.marvinkrausser.com {
|
||||
portfolio.marvinkrausser.com {
|
||||
reverse_proxy portfolio_website:8081
|
||||
}
|
||||
|
||||
http://localhost:8080 {
|
||||
reverse_proxy cnn_website:8080
|
||||
}
|
||||
|
||||
http://localhost:8000 {
|
||||
reverse_proxy cnn_api:8000
|
||||
}
|
||||
|
||||
http://localhost:8081 {
|
||||
reverse_proxy portfolio_website:8081
|
||||
}
|
||||
@@ -2,34 +2,6 @@
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
height: 40px;
|
||||
padding: 10px 20px;
|
||||
background: var(--surface);
|
||||
color: rgb(0, 0, 0);
|
||||
cursor: pointer;
|
||||
border-radius: 20px;
|
||||
border: 2px solid var(--surface);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.custom-button:hover {
|
||||
border-color: white;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.custom-button.inactive {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.custom-button.inactive:hover {
|
||||
border-color: var(--surface);
|
||||
}
|
||||
|
||||
.species-item {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
|
||||
@@ -3,7 +3,7 @@ import './Bird_CNN.css';
|
||||
|
||||
function Bird_CNN() {
|
||||
const apiUrl = process.env.NODE_ENV === "development"
|
||||
? "https://api.marvinkrausser.com"
|
||||
? "http://localhost:8000"
|
||||
: "https://api.marvinkrausser.com";
|
||||
|
||||
const [file, setFile] = useState(null);
|
||||
|
||||
+47
-8
@@ -1,3 +1,10 @@
|
||||
.review-description {
|
||||
margin: 70px;
|
||||
font-size: 1.1rem;
|
||||
width: min(800px, 70vw);
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
display: flex;
|
||||
gap: 80px;
|
||||
@@ -44,6 +51,7 @@
|
||||
height: fit-content;
|
||||
gap: 0px;
|
||||
user-select: none;
|
||||
--radius: 16px;
|
||||
}
|
||||
|
||||
.review-select-dropdown.rating {
|
||||
@@ -59,12 +67,12 @@
|
||||
outline: none;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
border-radius: 16px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.review-select-button.active {
|
||||
border-top-right-radius: 16px;
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: var(--radius);
|
||||
border-top-left-radius: var(--radius);
|
||||
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
@@ -75,11 +83,16 @@
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.review-select-menu.grid {
|
||||
.review-select-menu {
|
||||
width: 100%;
|
||||
grid-auto-rows: 1fr;
|
||||
}
|
||||
|
||||
.rating .review-select-menu {
|
||||
width: 300%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-auto-rows: 1fr;
|
||||
transform: translateX(-33.3333%);
|
||||
}
|
||||
|
||||
.review-select-item {
|
||||
@@ -95,9 +108,35 @@
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.review-select-item.last {
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
.rating .review-select-box-0 {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
border-bottom-left-radius: var(--radius);
|
||||
border-top-left-radius: var(--radius);
|
||||
}
|
||||
|
||||
.rating .review-select-box-1 {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.rating .review-select-box-2 {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
border-bottom-right-radius: var(--radius);
|
||||
border-top-right-radius: var(--radius);
|
||||
}
|
||||
|
||||
.rating .review-select-box-3 {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
border-bottom-left-radius: var(--radius);
|
||||
border-bottom-right-radius: var(--radius);
|
||||
}
|
||||
|
||||
.website .review-select-box-1 {
|
||||
border-bottom-left-radius: var(--radius);
|
||||
border-bottom-right-radius: var(--radius);
|
||||
}
|
||||
|
||||
.review-select-item:hover {
|
||||
|
||||
+25
-8
@@ -3,6 +3,10 @@ import './Reviews.css';
|
||||
import SelectDropdown from './components/SelectDropdown';
|
||||
|
||||
function Reviews() {
|
||||
const apiUrl = process.env.NODE_ENV === "development"
|
||||
? "http://localhost:8000"
|
||||
: "https://api.marvinkrausser.com";
|
||||
|
||||
const [error, setError] = useState(null);
|
||||
const [selectedWebsite, setSelectedWebsite] = useState("Not Specified");
|
||||
const [selectedRating, setSelectedRating] = useState(1);
|
||||
@@ -12,8 +16,11 @@ function Reviews() {
|
||||
|
||||
|
||||
const fetchReviews = async () => {
|
||||
const response = await fetch(`api.marvinkrausser/review`, {
|
||||
method: "GET",
|
||||
const response = await fetch(`${apiUrl}/review`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer 1234`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -24,12 +31,20 @@ function Reviews() {
|
||||
setError(false);
|
||||
}
|
||||
|
||||
console.log(response);
|
||||
|
||||
const result = await response.json();
|
||||
}
|
||||
|
||||
const sendReview = async (review) => {
|
||||
const sendReview = async () => {
|
||||
const review = {
|
||||
"website": selectedWebsite,
|
||||
"rating": selectedRating,
|
||||
"text": text,
|
||||
"created_at": "today"
|
||||
}
|
||||
|
||||
const response = await fetch(`api.marvinkrausser/review`, {
|
||||
const response = await fetch(`${apiUrl}/review`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(review),
|
||||
});
|
||||
@@ -47,12 +62,10 @@ function Reviews() {
|
||||
|
||||
const changeSelectedWebsite = (website) => {
|
||||
setSelectedWebsite(website);
|
||||
console.log(selectedWebsite);
|
||||
};
|
||||
|
||||
const changeSelectedRating = (rating) => {
|
||||
setSelectedRating(rating);
|
||||
console.log(selectedRating);
|
||||
}
|
||||
|
||||
const websites = [
|
||||
@@ -75,6 +88,7 @@ function Reviews() {
|
||||
return (<>
|
||||
<div className='site-box'>
|
||||
<h1 className='site-headline'>Reviews</h1>
|
||||
<p className='review-description'>I would greatly appreciate receiving a review. You are welcome to write it in either English or German. All fields are optional, and all reviews are submitted anonymously.</p>
|
||||
|
||||
|
||||
|
||||
@@ -88,7 +102,6 @@ function Reviews() {
|
||||
defaultValue={websites.at(-1)}
|
||||
classNames={"website"}
|
||||
changeInputParent={changeSelectedWebsite}
|
||||
flex={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -116,9 +129,13 @@ function Reviews() {
|
||||
defaultValue={ratings.at(0)}
|
||||
classNames={"rating"}
|
||||
changeInputParent={changeSelectedRating}
|
||||
flex={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button id='button-send' style={{ display: "none" }} onClick={sendReview} />
|
||||
<label htmlFor="button-send" className="custom-button">
|
||||
Send Review
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</>)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function DropwdownFlex(props) {
|
||||
const values = props.values;
|
||||
const classNames = props.classNames;
|
||||
const changeInputParent = props.changeInputParent;
|
||||
const selectedInput = props.selectedInput;
|
||||
|
||||
return (
|
||||
<div className={`review-select-menu flex ${classNames}`}>
|
||||
{values.map((item, i) =>
|
||||
String(selectedInput) !== String(item) && (
|
||||
<div
|
||||
key={item}
|
||||
className={i >= (values.length - (selectedInput == values.at(-1) ? 2 : 1)) ? `review-select-item ${classNames} last` : `review-select-item ${classNames}`}
|
||||
onClick={() => changeInputParent(item)}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropwdownFlex;
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function DropwdownGrid(props) {
|
||||
const values = props.values;
|
||||
const classNames = props.classNames;
|
||||
const changeInputParent = props.changeInputParent;
|
||||
const selectedInput = props.selectedInput;
|
||||
|
||||
return (
|
||||
<div className={`review-select-menu grid ${classNames}`}>
|
||||
{values.map((item, i) =>
|
||||
String(selectedInput) !== String(item) && (
|
||||
<div
|
||||
key={item}
|
||||
className={i >= (values.length - (selectedInput == values.at(-1) ? 2 : 1)) ? `review-select-item grid-box-${i} ${classNames} last` : `review-select-item grid-box-${i} ${classNames}`}
|
||||
onClick={() => changeInputParent(item)}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropwdownGrid;
|
||||
@@ -0,0 +1,3 @@
|
||||
.review-select-menu {
|
||||
display: grid;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import DropwdownFlex from './DropdownFlex';
|
||||
import DropwdownGrid from './DropdownGrid';
|
||||
import './SelectDropdown.css';
|
||||
|
||||
function SelectDropdown(props) {
|
||||
const values = props.values;
|
||||
@@ -8,17 +7,11 @@ function SelectDropdown(props) {
|
||||
const classNames = props.classNames;
|
||||
const changeInputParent = props.changeInputParent;
|
||||
const parentClass = props.parentClass;
|
||||
const flex = props.flex;
|
||||
const [selectVisibility, setSelectVisibility] = useState(false);
|
||||
const SelectButton = useRef();
|
||||
|
||||
const [selectedInput, setSelectedInput] = useState(defaultValue);
|
||||
|
||||
const SelectMenu =
|
||||
flex === true
|
||||
? DropwdownFlex
|
||||
: DropwdownGrid;
|
||||
|
||||
useEffect(() => {
|
||||
function handleClick(event) {
|
||||
if (SelectButton.current && !SelectButton.current.contains(event.target)
|
||||
@@ -39,6 +32,21 @@ function SelectDropdown(props) {
|
||||
changeInputParent(input);
|
||||
};
|
||||
|
||||
const CreateItems = () => {
|
||||
const indices = Array.from({ length: values.length - 1 }, (_, i) => values.length - 2 - i);
|
||||
return values.map((item, i) =>
|
||||
String(selectedInput) !== String(item) && (
|
||||
<div
|
||||
key={item}
|
||||
className={`review-select-item review-select-box-${indices.pop()} ${classNames}` + (i >= (values.length - (selectedInput == values.at(-1) ? 2 : 1)) ? ' last' : '')}
|
||||
onClick={() => changeSelectedInput(item)}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={`review-select-dropdown ${classNames} ${parentClass}`} >
|
||||
@@ -46,12 +54,9 @@ function SelectDropdown(props) {
|
||||
{selectedInput}
|
||||
</button>
|
||||
{selectVisibility &&
|
||||
<SelectMenu
|
||||
classNames={classNames}
|
||||
changeInputParent={changeSelectedInput}
|
||||
values={values}
|
||||
selectedInput={selectedInput}
|
||||
/>}
|
||||
<div className={`review-select-menu ${classNames}`}>
|
||||
<CreateItems />
|
||||
</div>}
|
||||
|
||||
</ div>
|
||||
);
|
||||
|
||||
@@ -57,3 +57,33 @@ h4 {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
height: 40px;
|
||||
padding: 10px 20px;
|
||||
background: var(--surface);
|
||||
color: rgb(0, 0, 0);
|
||||
cursor: pointer;
|
||||
border-radius: 20px;
|
||||
border: 2px solid var(--surface);
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.custom-button:hover {
|
||||
border-color: white;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.custom-button.inactive {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.custom-button.inactive:hover {
|
||||
border-color: var(--surface);
|
||||
}
|
||||
Reference in New Issue
Block a user