Add link as contact, ui fixes

This commit is contained in:
Adrian Woźniak 2022-08-05 14:25:50 +02:00
parent 6cff8e7a37
commit bbf774d8d9
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
17 changed files with 221 additions and 84 deletions

View File

@ -160,6 +160,9 @@ customElements.define('contact-info-editor', class extends Component {
if (s.match(/^[a-zA-Z\d.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*$/)) {
return 'email';
}
if (s.match(/https?:\/\//)) {
return 'link';
}
return 'other';
}
});

View File

@ -23,7 +23,8 @@ customElements.define('contact-type-icon',
:host([type="mobile"]) #mobile-icon {
display: block;
}
path {
:host([type='link']) #link-icon {
display: block;
}
</style>
@ -45,6 +46,9 @@ customElements.define('contact-type-icon',
<circle cx="272.723" cy="55.769" r="5.977"/>
<circle cx="312.426" cy="55.769" r="5.977"/>
</svg>
<svg id="link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 162.656 162.656" xml:space="preserve">
<path d="M151.764 10.894c-14.522-14.522-38.152-14.525-52.676-.008l.003.003-22.979 22.983 10.607 10.605 22.983-22.988-.002-.002c8.678-8.663 22.785-8.658 31.457.014 8.673 8.672 8.672 22.786 0 31.461l-34.486 34.484a22.095 22.095 0 0 1-15.729 6.516 22.098 22.098 0 0 1-15.73-6.516L64.605 98.052c7.035 7.035 16.389 10.91 26.338 10.91 9.949 0 19.303-3.875 26.335-10.91l34.487-34.484c14.519-14.525 14.519-38.155-.001-52.674z"/><path d="M52.96 141.162c-8.675 8.67-22.788 8.668-31.461-.005-8.673-8.675-8.673-22.791-.001-31.465L55.98 75.21c8.675-8.674 22.789-8.674 31.462 0L98.05 64.604c-14.524-14.523-38.154-14.524-52.676 0L10.89 99.086c-14.519 14.523-14.519 38.154.001 52.678 7.263 7.262 16.801 10.893 26.341 10.892 9.536 0 19.074-3.629 26.333-10.887l.002-.001 22.984-22.99-10.608-10.606-22.983 22.99z"/>
</svg>
`);
}

View File

@ -8,8 +8,15 @@ customElements.define('local-business-item', class extends Component {
constructor() {
super(`
<style>
:host { display: block; }
* { font-family: 'Cardo', sans-serif; }
:host {
display: block;
}
* {
font-family: 'Cardo', sans-serif;
--img-width: 128px;
--price-width: 160px;
--name-width: calc(100% - var(--price-width) - var(--img-width) - 20px);
}
:host([picture-url = '']) #img {
display: none;
}
@ -17,40 +24,47 @@ customElements.define('local-business-item', class extends Component {
display: grid;
grid-template-areas: 'img name' 'img price';
}
h3 {
h3#name {
font-weight: normal;
grid-area: name;
line-height: 1;
margin: 0;
text-align: right;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
width: var(--name-width);
}
#price {
grid-area: price; text-align: right;
grid-area: price;
text-align: right;
width: var(--price-width);
}
#img {
width: 128px;
max-width: 128px;
width: var(--img-width);
max-width: var(--img-width);
grid-area: img;
border-radius: 6px;
}
@media (min-width: 1200px) {
@media (min-width: 1000px) {
#item {
display: flex;
justify-content: space-between;
}
h3 {
h3#name {
font-weight: normal;
line-height: 1.6;
width: calc(100% - 450px);
width: var(--name-width);
text-align: left;
}
#price {
font-weight: bold;
width: 180px;
width: var(--price-width);
}
#img {
width: 128px;
width: var(--img-width);
max-width: 128px;
}
}

View File

@ -34,6 +34,10 @@ customElements.define('local-business-list', class extends Component {
::slotted(local-business) {
margin-bottom: 20px;
}
#search {
margin-bottom: 16px;;
margin-top: 16px;;
}
@media only screen and (min-device-width: 1000px) {
article {
margin: 0;
@ -51,7 +55,7 @@ customElements.define('local-business-list', class extends Component {
}
</style>
<article>
<section>
<section id="search">
<search-input target="local-business"></search-input>
</section>
<section id="items">

View File

@ -20,6 +20,9 @@ customElements.define('marketplace-offer', class extends Component {
section {
margin-bottom: 16px;
text-decoration: none;
color: black;
font-style: normal;
}
#preview {
@ -37,8 +40,7 @@ customElements.define('marketplace-offer', class extends Component {
}
:host([price-range-max="0"]) #sep, :host([price-range]) #sep,
:host([price-range-max="0"]) #price-min, :host([price-range]) #price-min
{
:host([price-range-max="0"]) #price-min, :host([price-range]) #price-min {
display: none;
}
@ -87,7 +89,7 @@ customElements.define('marketplace-offer', class extends Component {
justify-content: end;
}
@media only screen and (min-device-width: 1200px) {
@media only screen and (min-device-width: 1000px) {
#details {
display: grid;
column-gap: 16px;
@ -125,10 +127,17 @@ customElements.define('marketplace-offer', class extends Component {
width: 100px;
text-align: right;
}
a {
font-style: normal;
text-decoration: none;
color: black;
}
}
${ INPUT_STYLE }
</style>
<article>
<section id="details">
<div id="preview">
<image-popup src="" id="picture">
@ -143,6 +152,7 @@ customElements.define('marketplace-offer', class extends Component {
<span style="margin-right: 10px; font-weight: bold">Kontakt:</span>
<slot name="contacts"></slot>
</section>
</article>
`);
this.#price_range = new PriceRange(0, 0);
}

View File

@ -26,12 +26,19 @@ customElements.define('marketplace-offers', class extends Component {
:host([account-id]) #publishSection {
display: block;
}
::slotted(marketplace-offer), ::slotted(user-edit-offer) {
::slotted(a), ::slotted(marketplace-offer), ::slotted(user-edit-offer) {
margin-bottom: 20px;
text-decoration: none;
color: black;
font-style: normal;
}
::slotted([search-visible='invisible']) {
display: none;
}
#search {
margin-bottom: 16px;
margin-top: 16px;
}
@media only screen and (min-device-width: 1000px) {
#offers {
display: flex;
@ -39,9 +46,12 @@ customElements.define('marketplace-offers', class extends Component {
flex-wrap: wrap;
justify-items: stretch;
}
::slotted(marketplace-offer), ::slotted(user-edit-offer) {
::slotted(a), ::slotted(marketplace-offer), ::slotted(user-edit-offer) {
width: calc(33% - 40px);
margin: 0 20px 20px;
text-decoration: none;
color: black;
font-style: normal;
}
}
${ BUTTON_STYLE }
@ -51,7 +61,7 @@ customElements.define('marketplace-offers', class extends Component {
<button id="publish" class="btn">Dodaj ogłoszenie</button>
</section>
<section><slot></slot></section>
<section>
<section id="search">
<search-input target="user-edit-offer, marketplace-offer"></search-input>
</section>
<section id="offers">

View File

@ -15,6 +15,11 @@ customElements.define('user-edit-offer', class extends Component {
:host([mode='view']) #view { display: block; }
:host([mode='form']) #form { display: block; }
:host([state='Finished']) #finishForm { display: none; }
section, a, ::slotted(a) {
text-decoration: none;
color: black;
font-style: normal;
}
#actions {
display: flex;
justify-content: space-between;

View File

@ -25,8 +25,25 @@ customElements.define('search-input', class extends Component {
border-bottom: 1px solid #ccc;
text-indent: 20px;
}
svg { height: 24px; }
section {
display: flex;
justify-content: start;
align-content: center;
align-items: center;
}
</style>
<section>
<svg
viewBox="0 0 310.42 310.42"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
>
<path
d="M273.587 214.965c49.11-49.111 49.11-129.021 0-178.132s-129.021-49.111-178.131 0C53.792 78.497 47.482 140.462 76.509 188.85c0 0 2.085 3.496-.731 6.312l-64.263 64.263c-12.791 12.79-15.837 30.675-4.493 42.02l1.953 1.951c11.343 11.345 29.229 8.301 42.019-4.49l64.128-64.128c2.951-2.951 6.448-.866 6.448-.866 48.387 29.026 110.353 22.717 152.017-18.947zM118.71 191.71c-36.288-36.288-36.287-95.332 0-131.62 36.288-36.287 95.333-36.288 131.62 0 36.288 36.287 36.288 95.332 0 131.62-36.287 36.287-95.331 36.287-131.62 0z"
/>
</svg>
<input type="text" id="filter" placeholder="Znajdź (wyrażenia regularne są wspierane)" />
</section>
`);
@ -52,10 +69,8 @@ customElements.define('search-input', class extends Component {
super.connectedCallback();
let node = this;
while (node) {
console.warn(node)
if (node == null) return console.warn('no parent node', node);
if (node instanceof ShadowRoot) {
console.warn('node is shadow')
this.#host = node.host;
break;
}

View File

@ -26,7 +26,7 @@
mode="icon"
contact-id="{{contact.id}}"
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
contact-type="{{contact.contact_type}}"
type="{{contact.contact_type}}"
></contact-info>
{% endfor %}
</contact-info-list>

View File

@ -25,7 +25,7 @@
mode="icon"
contact-id="{{contact.id}}"
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
contact-type="{{contact.contact_type}}"
type="{{contact.contact_type}}"
></contact-info>
{% endfor %}
</contact-info-list>

View File

@ -22,6 +22,7 @@
price-range-max="{{max}}"
{% endmatch %}
>
<a href="/marketplace/{{offer.id}}">
<marketplace-offer
offer-id="{{offer.id}}"
description="{{offer.description}}"
@ -49,12 +50,14 @@
{% endfor %}
</contact-info-list>
</marketplace-offer>
</a>
</user-edit-offer>
{% endfor %}
{% for offer in offers %}
<a href="/marketplace/{{offer.id}}" slot="offer">
<marketplace-offer
slot="offer"
offer-id="{{offer.id}}"
description="{{offer.description}}"
picture-url="{{offer.picture_url}}"
@ -79,6 +82,7 @@
{% endfor %}
</contact-info-list>
</marketplace-offer>
</a>
{% endfor %}
</marketplace-offers>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "../base.html" %}
{% block content %}
<marketplace-offer
{{h.account_id_tag(account)}}
offer-id="{{offer.id}}"
description="{{offer.description}}"
picture-url="{{offer.picture_url}}"
{% match offer.price_range %}
{% when PriceRange::Free %}
price-range-min="0"
price-range-max="0"
{% when PriceRange::Fixed with { value } %}
price-range-min="{{value}}"
price-range-max="0"
{% when PriceRange::Range with { min, max } %}
price-range-min="{{min}}"
price-range-max="{{max}}"
{% endmatch %}
>
<contact-info-list slot="contacts">
{% for contact in offer.contacts %}
<contact-info
mode="icon"
contact-id="{{contact.id}}"
type="{{contact.contact_type}}"
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
></contact-info>
{% endfor %}
</contact-info-list>
</marketplace-offer>
{% endblock %}

View File

@ -14,6 +14,7 @@ SELECT
content
FROM
contacts
ORDER BY contact_type, id ASC
"#,
)
.fetch_all(t)

View File

@ -57,8 +57,13 @@ ORDER BY id DESC
}
#[tracing::instrument]
pub async fn offer_by_id(t: &mut T<'_>, account_id: i32, offer_id: i32) -> Result<db::Offer> {
sqlx::query_as(
pub async fn offer_by_id(
t: &mut T<'_>,
account_id: Option<i32>,
offer_id: i32,
) -> Result<db::Offer> {
match account_id {
Some(account_id) => sqlx::query_as(
r#"
SELECT
id,
@ -74,7 +79,24 @@ LIMIT 1
"#,
)
.bind(account_id)
.bind(offer_id)
.bind(offer_id),
None => sqlx::query_as(
r#"
SELECT
id,
owner_id,
price_range,
description,
picture_url,
state,
created_at
FROM offers
WHERE id = $1
LIMIT 1
"#,
)
.bind(offer_id),
}
.fetch_one(t)
.await
.map_err(|e| {

View File

@ -314,9 +314,15 @@ impl Responder for HttpResult {
let status_code = self.status_code();
match self {
HttpResult::Json { body, code } => HttpResponse::build(code)
.append_header(("Sec-GPC-field-name", "Sec-GPC"))
.append_header(("Sec-GPC-field-value", "1"))
.append_header(("Sec-GPC", "1"))
.content_type("application/json")
.body(body),
HttpResult::Html { body, code } => HttpResponse::build(code)
.append_header(("Sec-GPC-field-name", "Sec-GPC"))
.append_header(("Sec-GPC-field-value", "1"))
.append_header(("Sec-GPC", "1"))
.content_type("text/html")
.body(body),
HttpResult::Err {

View File

@ -37,6 +37,13 @@ pub async fn render_index() -> HttpResponse {
)
}
#[get("/.well-known/gpc.json")]
async fn gpc() -> HttpResponse {
HttpResponse::Ok()
.content_type("application/json")
.body("{\"gpc\":true,\"lastUpdate\":\"1997-03-10\"}")
}
#[get("/")]
async fn index(req: HttpRequest) -> HttpResult {
HttpResult::goto(&req, "/marketplace")
@ -62,5 +69,6 @@ pub fn configure(config: &mut ServiceConfig) {
.prefer_utf8(true)
.show_files_listing(),
)
.service(index);
.service(index)
.service(gpc);
}

View File

@ -114,7 +114,7 @@ async fn edit_marketplace(
}
};
let offer = match queries::offer_by_id(&mut t, account.id, offer_id).await {
let offer = match queries::offer_by_id(&mut t, Some(account.id), offer_id).await {
Ok(offer) => offer,
Err(e) => {
dbg!(e);
@ -146,7 +146,7 @@ async fn edit_marketplace(
}
#[derive(Default, Serialize, Template)]
#[template(path = "./marketplace/edit.html")]
#[template(path = "./marketplace/show.html")]
struct ShowOfferTemplate {
account: Option<db::Account>,
error: Option<String>,
@ -176,7 +176,7 @@ async fn show_marketplace(
return HttpResult::res(
&req,
StatusCode::NOT_FOUND,
EditOfferTemplate {
ShowOfferTemplate {
page: Page::Marketplace,
account,
error: Some("Oferta nie istnieje".into()),
@ -186,14 +186,14 @@ async fn show_marketplace(
}
};
let offer = match queries::offer_by_id(&mut t, account.id, offer_id).await {
let offer = match queries::offer_by_id(&mut t, None, offer_id).await {
Ok(offer) => offer,
Err(e) => {
dbg!(e);
return HttpResult::res(
&req,
StatusCode::NOT_FOUND,
EditOfferTemplate {
ShowOfferTemplate {
page: Page::Marketplace,
account: Some(account),
error: Some("Oferta nie istnieje".into()),
@ -208,7 +208,7 @@ async fn show_marketplace(
HttpResult::res(
&req,
StatusCode::OK,
EditOfferTemplate {
ShowOfferTemplate {
page: Page::Marketplace,
account: Some(account),
offer: view::Offer::from((offer, &vec![])),