better review design, caddy now listens on localhost too
This commit is contained in:
@@ -8,4 +8,16 @@ api.marvinkrausser.com {
|
|||||||
|
|
||||||
portfolio.marvinkrausser.com {
|
portfolio.marvinkrausser.com {
|
||||||
reverse_proxy portfolio_website:8081
|
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;
|
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 {
|
.species-item {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import './Bird_CNN.css';
|
|||||||
|
|
||||||
function Bird_CNN() {
|
function Bird_CNN() {
|
||||||
const apiUrl = process.env.NODE_ENV === "development"
|
const apiUrl = process.env.NODE_ENV === "development"
|
||||||
? "https://api.marvinkrausser.com"
|
? "http://localhost:8000"
|
||||||
: "https://api.marvinkrausser.com";
|
: "https://api.marvinkrausser.com";
|
||||||
|
|
||||||
const [file, setFile] = useState(null);
|
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 {
|
.input-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 80px;
|
gap: 80px;
|
||||||
@@ -44,6 +51,7 @@
|
|||||||
height: fit-content;
|
height: fit-content;
|
||||||
gap: 0px;
|
gap: 0px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
--radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-select-dropdown.rating {
|
.review-select-dropdown.rating {
|
||||||
@@ -59,12 +67,12 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
color: black;
|
color: black;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: 16px;
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-select-button.active {
|
.review-select-button.active {
|
||||||
border-top-right-radius: 16px;
|
border-top-right-radius: var(--radius);
|
||||||
border-top-left-radius: 16px;
|
border-top-left-radius: var(--radius);
|
||||||
|
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
@@ -75,11 +83,16 @@
|
|||||||
border-color: white;
|
border-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-select-menu.grid {
|
.review-select-menu {
|
||||||
|
width: 100%;
|
||||||
|
grid-auto-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating .review-select-menu {
|
||||||
width: 300%;
|
width: 300%;
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
|
transform: translateX(-33.3333%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-select-item {
|
.review-select-item {
|
||||||
@@ -95,9 +108,35 @@
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-select-item.last {
|
.rating .review-select-box-0 {
|
||||||
border-bottom-left-radius: 16px;
|
grid-column: 1;
|
||||||
border-bottom-right-radius: 16px;
|
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 {
|
.review-select-item:hover {
|
||||||
|
|||||||
+25
-8
@@ -3,6 +3,10 @@ import './Reviews.css';
|
|||||||
import SelectDropdown from './components/SelectDropdown';
|
import SelectDropdown from './components/SelectDropdown';
|
||||||
|
|
||||||
function Reviews() {
|
function Reviews() {
|
||||||
|
const apiUrl = process.env.NODE_ENV === "development"
|
||||||
|
? "http://localhost:8000"
|
||||||
|
: "https://api.marvinkrausser.com";
|
||||||
|
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [selectedWebsite, setSelectedWebsite] = useState("Not Specified");
|
const [selectedWebsite, setSelectedWebsite] = useState("Not Specified");
|
||||||
const [selectedRating, setSelectedRating] = useState(1);
|
const [selectedRating, setSelectedRating] = useState(1);
|
||||||
@@ -12,8 +16,11 @@ function Reviews() {
|
|||||||
|
|
||||||
|
|
||||||
const fetchReviews = async () => {
|
const fetchReviews = async () => {
|
||||||
const response = await fetch(`api.marvinkrausser/review`, {
|
const response = await fetch(`${apiUrl}/review`, {
|
||||||
method: "GET",
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer 1234`,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -24,12 +31,20 @@ function Reviews() {
|
|||||||
setError(false);
|
setError(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
const result = await response.json();
|
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",
|
method: "POST",
|
||||||
body: JSON.stringify(review),
|
body: JSON.stringify(review),
|
||||||
});
|
});
|
||||||
@@ -47,12 +62,10 @@ function Reviews() {
|
|||||||
|
|
||||||
const changeSelectedWebsite = (website) => {
|
const changeSelectedWebsite = (website) => {
|
||||||
setSelectedWebsite(website);
|
setSelectedWebsite(website);
|
||||||
console.log(selectedWebsite);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeSelectedRating = (rating) => {
|
const changeSelectedRating = (rating) => {
|
||||||
setSelectedRating(rating);
|
setSelectedRating(rating);
|
||||||
console.log(selectedRating);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const websites = [
|
const websites = [
|
||||||
@@ -75,6 +88,7 @@ function Reviews() {
|
|||||||
return (<>
|
return (<>
|
||||||
<div className='site-box'>
|
<div className='site-box'>
|
||||||
<h1 className='site-headline'>Reviews</h1>
|
<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)}
|
defaultValue={websites.at(-1)}
|
||||||
classNames={"website"}
|
classNames={"website"}
|
||||||
changeInputParent={changeSelectedWebsite}
|
changeInputParent={changeSelectedWebsite}
|
||||||
flex={true}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -116,9 +129,13 @@ function Reviews() {
|
|||||||
defaultValue={ratings.at(0)}
|
defaultValue={ratings.at(0)}
|
||||||
classNames={"rating"}
|
classNames={"rating"}
|
||||||
changeInputParent={changeSelectedRating}
|
changeInputParent={changeSelectedRating}
|
||||||
flex={false}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button id='button-send' style={{ display: "none" }} onClick={sendReview} />
|
||||||
|
<label htmlFor="button-send" className="custom-button">
|
||||||
|
Send Review
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</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 { useState, useRef, useEffect } from 'react';
|
||||||
import DropwdownFlex from './DropdownFlex';
|
import './SelectDropdown.css';
|
||||||
import DropwdownGrid from './DropdownGrid';
|
|
||||||
|
|
||||||
function SelectDropdown(props) {
|
function SelectDropdown(props) {
|
||||||
const values = props.values;
|
const values = props.values;
|
||||||
@@ -8,17 +7,11 @@ function SelectDropdown(props) {
|
|||||||
const classNames = props.classNames;
|
const classNames = props.classNames;
|
||||||
const changeInputParent = props.changeInputParent;
|
const changeInputParent = props.changeInputParent;
|
||||||
const parentClass = props.parentClass;
|
const parentClass = props.parentClass;
|
||||||
const flex = props.flex;
|
|
||||||
const [selectVisibility, setSelectVisibility] = useState(false);
|
const [selectVisibility, setSelectVisibility] = useState(false);
|
||||||
const SelectButton = useRef();
|
const SelectButton = useRef();
|
||||||
|
|
||||||
const [selectedInput, setSelectedInput] = useState(defaultValue);
|
const [selectedInput, setSelectedInput] = useState(defaultValue);
|
||||||
|
|
||||||
const SelectMenu =
|
|
||||||
flex === true
|
|
||||||
? DropwdownFlex
|
|
||||||
: DropwdownGrid;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClick(event) {
|
function handleClick(event) {
|
||||||
if (SelectButton.current && !SelectButton.current.contains(event.target)
|
if (SelectButton.current && !SelectButton.current.contains(event.target)
|
||||||
@@ -39,6 +32,21 @@ function SelectDropdown(props) {
|
|||||||
changeInputParent(input);
|
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 (
|
return (
|
||||||
<div className={`review-select-dropdown ${classNames} ${parentClass}`} >
|
<div className={`review-select-dropdown ${classNames} ${parentClass}`} >
|
||||||
@@ -46,12 +54,9 @@ function SelectDropdown(props) {
|
|||||||
{selectedInput}
|
{selectedInput}
|
||||||
</button>
|
</button>
|
||||||
{selectVisibility &&
|
{selectVisibility &&
|
||||||
<SelectMenu
|
<div className={`review-select-menu ${classNames}`}>
|
||||||
classNames={classNames}
|
<CreateItems />
|
||||||
changeInputParent={changeSelectedInput}
|
</div>}
|
||||||
values={values}
|
|
||||||
selectedInput={selectedInput}
|
|
||||||
/>}
|
|
||||||
|
|
||||||
</ div>
|
</ div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -56,4 +56,34 @@ h4 {
|
|||||||
margin-top: 150px;
|
margin-top: 150px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: fit-content;
|
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