Refactor SelectField and allow headers with CORS
This commit is contained in:
parent
e9945a19d5
commit
9919e0c16e
|
|
@ -3,7 +3,10 @@ use std::{env, net::SocketAddr, sync::Arc};
|
||||||
use axum::{
|
use axum::{
|
||||||
error_handling::HandleErrorLayer,
|
error_handling::HandleErrorLayer,
|
||||||
extract,
|
extract,
|
||||||
http::{HeaderValue, Method},
|
http::{
|
||||||
|
header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE},
|
||||||
|
HeaderValue, Method,
|
||||||
|
},
|
||||||
routing::{get, patch, post},
|
routing::{get, patch, post},
|
||||||
BoxError, Router, Server,
|
BoxError, Router, Server,
|
||||||
};
|
};
|
||||||
|
|
@ -44,6 +47,8 @@ async fn main() {
|
||||||
|
|
||||||
// CORS configuration
|
// CORS configuration
|
||||||
let cors = CorsLayer::new()
|
let cors = CorsLayer::new()
|
||||||
|
.allow_credentials(true)
|
||||||
|
.allow_headers([AUTHORIZATION, ACCEPT, CONTENT_TYPE])
|
||||||
.allow_methods([Method::GET, Method::POST, Method::PATCH])
|
.allow_methods([Method::GET, Method::POST, Method::PATCH])
|
||||||
.allow_origin(
|
.allow_origin(
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation'
|
||||||
import Button from '/src/components/Button/Button'
|
import Button from '/src/components/Button/Button'
|
||||||
import CalendarField from '/src/components/CalendarField/CalendarField'
|
import CalendarField from '/src/components/CalendarField/CalendarField'
|
||||||
import { default as ErrorAlert } from '/src/components/Error/Error'
|
import { default as ErrorAlert } from '/src/components/Error/Error'
|
||||||
|
import SelectField from '/src/components/SelectField/SelectField'
|
||||||
import TextField from '/src/components/TextField/TextField'
|
import TextField from '/src/components/TextField/TextField'
|
||||||
import TimeRangeField from '/src/components/TimeRangeField/TimeRangeField'
|
import TimeRangeField from '/src/components/TimeRangeField/TimeRangeField'
|
||||||
import { API_BASE } from '/src/config/api'
|
import { API_BASE } from '/src/config/api'
|
||||||
|
|
@ -148,13 +149,13 @@ const CreateForm = () => {
|
||||||
name="time"
|
name="time"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <SelectField
|
<SelectField
|
||||||
label={t('form.timezone.label')}
|
label={t('form.timezone.label')}
|
||||||
options={timezones}
|
options={timezones}
|
||||||
required
|
required
|
||||||
{...register('timezone')}
|
{...register('timezone')}
|
||||||
defaultOption={t('form.timezone.defaultOption')}
|
defaultOption={t('form.timezone.defaultOption')}
|
||||||
/> */}
|
/>
|
||||||
|
|
||||||
<ErrorAlert onClose={() => setError(undefined)}>{error}</ErrorAlert>
|
<ErrorAlert onClose={() => setError(undefined)}>{error}</ErrorAlert>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { forwardRef } from 'react'
|
|
||||||
|
|
||||||
import {
|
|
||||||
Wrapper,
|
|
||||||
StyledLabel,
|
|
||||||
StyledSubLabel,
|
|
||||||
StyledSelect,
|
|
||||||
} from './SelectField.styles'
|
|
||||||
|
|
||||||
const SelectField = forwardRef(({
|
|
||||||
label,
|
|
||||||
subLabel,
|
|
||||||
id,
|
|
||||||
options = [],
|
|
||||||
inline = false,
|
|
||||||
small = false,
|
|
||||||
defaultOption,
|
|
||||||
...props
|
|
||||||
}, ref) => (
|
|
||||||
<Wrapper $inline={inline} $small={small}>
|
|
||||||
{label && <StyledLabel htmlFor={id} $inline={inline} $small={small}>{label}</StyledLabel>}
|
|
||||||
{subLabel && <StyledSubLabel htmlFor={id}>{subLabel}</StyledSubLabel>}
|
|
||||||
|
|
||||||
<StyledSelect
|
|
||||||
id={id}
|
|
||||||
$small={small}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{defaultOption && <option value="">{defaultOption}</option>}
|
|
||||||
{Array.isArray(options) ? (
|
|
||||||
options.map(value =>
|
|
||||||
<option key={value} value={value}>{value}</option>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
Object.entries(options).map(([key, value]) =>
|
|
||||||
<option key={key} value={key}>{value}</option>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</StyledSelect>
|
|
||||||
</Wrapper>
|
|
||||||
))
|
|
||||||
|
|
||||||
export default SelectField
|
|
||||||
23
frontend/src/components/SelectField/SelectField.module.scss
Normal file
23
frontend/src/components/SelectField/SelectField.module.scss
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
.select {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font: inherit;
|
||||||
|
background: var(--surface);
|
||||||
|
color: inherit;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 1px solid var(--primary);
|
||||||
|
box-shadow: inset 0 0 0 0 var(--primary);
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color .15s, box-shadow .15s;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%3E%3CforeignObject%20width%3D%22100px%22%20height%3D%22100px%22%3E%3Cdiv%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%20style%3D%22color%3A%23F79E00%3Bfont-size%3A60px%3Bdisplay%3Aflex%3Balign-items%3Acenter%3Bjustify-content%3Acenter%3Bheight%3A100%25%3Bwidth%3A100%25%3B%22%3E%E2%96%BC%3C%2Fdiv%3E%3C%2FforeignObject%3E%3C%2Fsvg%3E');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 10px center;
|
||||||
|
background-size: 1em;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: 1px solid var(--secondary);
|
||||||
|
box-shadow: inset 0 -3px 0 0 var(--secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import { styled } from 'goober'
|
|
||||||
import { forwardRef } from 'react'
|
|
||||||
|
|
||||||
export const Wrapper = styled('div')`
|
|
||||||
margin: 30px 0;
|
|
||||||
|
|
||||||
${props => props.$inline && `
|
|
||||||
margin: 0;
|
|
||||||
`}
|
|
||||||
${props => props.$small && `
|
|
||||||
margin: 10px 0;
|
|
||||||
`}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const StyledLabel = styled('label')`
|
|
||||||
display: block;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
font-size: 18px;
|
|
||||||
|
|
||||||
${props => props.$inline && `
|
|
||||||
font-size: 16px;
|
|
||||||
`}
|
|
||||||
${props => props.$small && `
|
|
||||||
font-size: .9rem;
|
|
||||||
`}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const StyledSubLabel = styled('label')`
|
|
||||||
display: block;
|
|
||||||
padding-bottom: 6px;
|
|
||||||
font-size: 13px;
|
|
||||||
opacity: .6;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const StyledSelect = styled('select', forwardRef)`
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font: inherit;
|
|
||||||
background: var(--surface);
|
|
||||||
color: inherit;
|
|
||||||
padding: 10px 14px;
|
|
||||||
border: 1px solid var(--primary);
|
|
||||||
box-shadow: inset 0 0 0 0 var(--primary);
|
|
||||||
border-radius: 3px;
|
|
||||||
outline: none;
|
|
||||||
transition: border-color .15s, box-shadow .15s;
|
|
||||||
appearance: none;
|
|
||||||
background-image: url('data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><foreignObject width="100px" height="100px"><div xmlns="http://www.w3.org/1999/xhtml" style="color:#F79E00;font-size:60px;display:flex;align-items:center;justify-content:center;height:100%;width:100%;">▼</div></foreignObject></svg>')}');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right 10px center;
|
|
||||||
background-size: 1em;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border: 1px solid var(--secondary);
|
|
||||||
box-shadow: inset 0 -3px 0 0 var(--secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
${props => props.$small && `
|
|
||||||
padding: 6px 8px;
|
|
||||||
`}
|
|
||||||
`
|
|
||||||
52
frontend/src/components/SelectField/SelectField.tsx
Normal file
52
frontend/src/components/SelectField/SelectField.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
|
import { Description, Label, Wrapper } from '/src/components/Field/Field'
|
||||||
|
|
||||||
|
import styles from './SelectField.module.scss'
|
||||||
|
|
||||||
|
interface SelectFieldProps extends React.ComponentProps<'select'> {
|
||||||
|
label?: React.ReactNode
|
||||||
|
description?: React.ReactNode
|
||||||
|
options: Record<string, React.ReactNode> | string[]
|
||||||
|
isInline?: boolean
|
||||||
|
isSmall?: boolean
|
||||||
|
defaultOption?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectField = forwardRef<HTMLSelectElement, SelectFieldProps>(({
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
options = [],
|
||||||
|
isInline = false,
|
||||||
|
isSmall = false,
|
||||||
|
defaultOption,
|
||||||
|
...props
|
||||||
|
}, ref) => <Wrapper style={isInline ? { margin: 0 } : (isSmall ? { marginBlock: '10px' } : undefined)}>
|
||||||
|
{label && <Label
|
||||||
|
htmlFor={props.name}
|
||||||
|
style={isInline ? { fontSize: '16px' } : (isSmall ? { fontSize: '.9rem' } : undefined)}
|
||||||
|
>{label}</Label>}
|
||||||
|
|
||||||
|
{description && <Description htmlFor={props.name}>{description}</Description>}
|
||||||
|
|
||||||
|
<select
|
||||||
|
className={styles.select}
|
||||||
|
id={props.name}
|
||||||
|
style={isSmall ? { padding: '6px 8px' } : undefined}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{defaultOption && <option value="">{defaultOption}</option>}
|
||||||
|
{Array.isArray(options) ? (
|
||||||
|
options.map(value =>
|
||||||
|
<option key={value} value={value}>{value}</option>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
Object.entries(options).map(([key, value]) =>
|
||||||
|
<option key={key} value={key}>{value}</option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
</Wrapper>)
|
||||||
|
|
||||||
|
export default SelectField
|
||||||
Loading…
Reference in a new issue