Skip to content

Commit 7ecff97

Browse files
committed
Fix up percentage slider to match csfloat behavior. Add subtotal + sales fee section. Clean up some hardcoded consts
1 parent 083493a commit 7ecff97

File tree

2 files changed

+248
-33
lines changed

2 files changed

+248
-33
lines changed

src/environment.dev.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export const environment = {
2-
csfloat_base_api_url: 'http://localhost:8080/api',
2+
csfloat_base_api_url: 'https://csfloat.com/api',
33
};

src/lib/components/inventory/list_item_modal.ts

Lines changed: 247 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export class ListItemModal extends FloatElement {
4040
@state()
4141
private listingId: string | undefined;
4242

43+
private readonly MAX_PRICE_CENTS = 100000 * 100; // $100,000
44+
45+
private readonly SALES_FEE_PERCENTAGE = 0.02;
46+
4347
private readonly DURATION_OPTIONS = [
4448
{value: 1, label: '1 Day'},
4549
{value: 3, label: '3 Days'},
@@ -70,7 +74,7 @@ export class ListItemModal extends FloatElement {
7074
padding: 20px;
7175
width: 500px;
7276
max-width: 90%;
73-
font-family: 'Roboto', sans-serif;
77+
font-family: Roboto, "Helvetica Neue", sans-serif;
7478
border-width: 2px;
7579
border-style: solid;
7680
border-color: rgba(193, 206, 255, 0.07);
@@ -117,27 +121,157 @@ export class ListItemModal extends FloatElement {
117121
118122
.price-section {
119123
margin-bottom: 20px;
124+
color: rgba(255, 255, 255, 0.8);
125+
font-size: 14px;
126+
}
127+
128+
.price-input-container {
129+
position: relative;
130+
margin-top: 8px;
131+
}
132+
133+
.price-input-prefix {
134+
position: absolute;
135+
left: 12px;
136+
top: 50%;
137+
transform: translateY(-50%);
138+
color: rgba(255, 255, 255, 0.8);
139+
font-size: 14px;
140+
pointer-events: none;
120141
}
121142
122143
.price-input {
123144
width: 100%;
124-
padding: 8px;
125-
margin-top: 5px;
126-
background: #2a475e;
127-
border: 1px solid #000000;
128-
color: #ffffff;
145+
box-sizing: border-box;
146+
padding: 12px;
147+
padding-left: 28px;
148+
background: rgba(35, 123, 255, 0.1);
149+
border: none;
150+
border-radius: 8px;
151+
color: white;
152+
font-size: 14px;
153+
font-weight: 500;
154+
font-family: 'Roboto', sans-serif;
155+
transition: background 0.2s ease;
156+
}
157+
158+
.price-input::placeholder {
159+
color: rgba(255, 255, 255, 0.4);
160+
}
161+
162+
.price-input:focus {
163+
outline: none;
164+
background: rgba(35, 123, 255, 0.15);
165+
}
166+
167+
.price-input::-webkit-outer-spin-button,
168+
.price-input::-webkit-inner-spin-button {
169+
-webkit-appearance: none;
170+
margin: 0;
171+
}
172+
173+
.price-input[type='number'] {
174+
-moz-appearance: textfield;
129175
}
130176
131177
.percentage-slider {
132178
width: 100%;
133-
margin-top: 10px;
179+
margin: 16px 0;
180+
height: 8px;
181+
background: rgba(35, 123, 255, 0.15);
182+
border-radius: 4px;
183+
-webkit-appearance: none;
184+
appearance: none;
185+
cursor: pointer;
186+
outline: none;
187+
position: relative;
188+
}
189+
190+
.percentage-slider::before {
191+
content: '';
192+
position: absolute;
193+
height: 100%;
194+
width: calc(var(--slider-percentage, 100) * 1%);
195+
background-color: rgb(35, 123, 255);
196+
border-radius: 4px;
197+
pointer-events: none;
198+
}
199+
200+
.percentage-slider::-webkit-slider-thumb {
201+
-webkit-appearance: none;
202+
appearance: none;
203+
width: 20px;
204+
height: 20px;
205+
border-radius: 50%;
206+
background: rgb(35, 123, 255);
207+
cursor: pointer;
208+
box-shadow: 0 2px 6px rgba(35, 123, 255, 0.3);
209+
margin-top: -6px;
210+
position: relative;
211+
z-index: 1;
212+
}
213+
214+
.percentage-slider::-webkit-slider-runnable-track {
215+
width: 100%;
216+
height: 8px;
217+
border-radius: 4px;
218+
background: transparent;
219+
}
220+
221+
.percentage-slider::-moz-range-thumb {
222+
width: 20px;
223+
height: 20px;
224+
border: none;
225+
border-radius: 50%;
226+
background: rgb(35, 123, 255);
227+
cursor: pointer;
228+
box-shadow: 0 2px 6px rgba(35, 123, 255, 0.3);
229+
position: relative;
230+
z-index: 1;
231+
}
232+
233+
.percentage-slider::-moz-range-track {
234+
width: 100%;
235+
height: 8px;
236+
border-radius: 4px;
237+
background: transparent;
238+
}
239+
240+
.percentage-slider::-webkit-slider-thumb:hover,
241+
.percentage-slider::-moz-range-thumb:hover {
242+
transform: scale(1.2);
134243
}
135244
136245
.error-message {
137246
color: #ff4444;
138247
margin-top: 10px;
139248
}
140249
250+
.price-breakdown {
251+
margin: 24px 0;
252+
}
253+
254+
.price-breakdown-row {
255+
display: flex;
256+
justify-content: space-between;
257+
align-items: center;
258+
margin-bottom: 8px;
259+
color: rgb(158, 167, 177)
260+
font-size: 16px;
261+
}
262+
263+
.price-breakdown-row:last-child {
264+
margin-bottom: 0;
265+
padding-top: 8px;
266+
border-top: 1px solid rgba(255, 255, 255, 0.1);
267+
color: #FFFFFF;
268+
font-size: 20px
269+
}
270+
271+
.price-breakdown-row.fee {
272+
color: rgba(255, 0, 0, 0.8);
273+
}
274+
141275
.submit-button {
142276
width: 100%;
143277
padding: 12px;
@@ -148,7 +282,6 @@ export class ListItemModal extends FloatElement {
148282
font-size: 14px;
149283
font-weight: 500;
150284
cursor: pointer;
151-
margin-top: 24px;
152285
transition: all 0.2s ease;
153286
box-shadow: 0 4px 12px rgba(35, 123, 255, 0.3);
154287
}
@@ -330,6 +463,13 @@ export class ListItemModal extends FloatElement {
330463

331464
async connectedCallback() {
332465
super.connectedCallback();
466+
// Set initial slider progress
467+
requestAnimationFrame(() => {
468+
const slider = this.shadowRoot?.querySelector('.percentage-slider') as HTMLInputElement;
469+
if (slider) {
470+
slider.style.setProperty('--slider-percentage', '50');
471+
}
472+
});
333473
await this.fetchRecommendedPrice();
334474
}
335475

@@ -360,8 +500,7 @@ export class ListItemModal extends FloatElement {
360500
return {isValid: false, error: 'Please enter a valid price greater than $0.00'};
361501
}
362502

363-
if (price > 10000000) {
364-
// $100,000 in cents
503+
if (price > this.MAX_PRICE_CENTS) {
365504
return {isValid: false, error: 'Price cannot exceed $100,000 USD'};
366505
}
367506

@@ -378,22 +517,69 @@ export class ListItemModal extends FloatElement {
378517

379518
this.error = undefined;
380519
this.customPrice = price;
381-
if (this.recommendedPrice) {
382-
this.pricePercentage = Number(((this.customPrice / this.recommendedPrice) * 100).toFixed(1));
383-
}
520+
}
521+
522+
private getSaleFee(cents: number): number {
523+
return Math.max(1, cents * this.SALES_FEE_PERCENTAGE);
524+
}
525+
526+
private formatPrice(cents: number): string {
527+
return (cents / 100).toFixed(2);
528+
}
529+
530+
private formatInputPrice(cents: number): string {
531+
// For input, show the exact value without forcing decimals
532+
const dollars = (cents / 100).toString();
533+
// Remove trailing .00 if it exists
534+
return dollars.replace(/\.?0+$/, '');
384535
}
385536

386537
private handlePriceChange(e: Event) {
387-
const value = (e.target as HTMLInputElement).value;
388-
const price = Math.round(Number(parseFloat(value)) * 100);
389-
this.updatePrice(price);
538+
const input = e.target as HTMLInputElement;
539+
let value = input.value;
540+
541+
// Remove any non-numeric or non-decimal characters
542+
value = value.replace(/[^\d.]/g, '');
543+
544+
// Ensure only one decimal point
545+
const parts = value.split('.');
546+
if (parts.length > 2) {
547+
value = parts[0] + '.' + parts.slice(1).join('');
548+
}
549+
550+
// Limit decimal places to 2
551+
if (parts.length === 2) {
552+
value = parts[0] + '.' + parts[1].slice(0, 2);
553+
}
554+
555+
// Update the input value
556+
input.value = value;
557+
558+
// Convert to cents for storage
559+
const dollars = parseFloat(value || '0');
560+
if (dollars * 100 > this.MAX_PRICE_CENTS) {
561+
input.value = (this.MAX_PRICE_CENTS / 100).toString();
562+
this.updatePrice(this.MAX_PRICE_CENTS);
563+
} else {
564+
const cents = Math.ceil(dollars * 100);
565+
this.updatePrice(Math.max(1, cents));
566+
}
390567
}
391568

392569
private handlePercentageChange(e: Event) {
393-
const value = (e.target as HTMLInputElement).value;
394-
this.pricePercentage = Number(parseFloat(value).toFixed(1));
570+
const input = e.target as HTMLInputElement;
571+
const value = parseFloat(input.value);
572+
this.pricePercentage = value;
573+
574+
// Update the slider progress - normalize to 0-100 based on min-max range
575+
requestAnimationFrame(() => {
576+
const normalizedValue = ((value - 80) / (120 - 80)) * 100;
577+
input.style.setProperty('--slider-percentage', normalizedValue.toString());
578+
});
579+
395580
if (this.recommendedPrice) {
396-
const newPrice = Math.round((this.pricePercentage / 100) * this.recommendedPrice);
581+
const exactPrice = (value / 100) * this.recommendedPrice;
582+
const newPrice = Math.ceil(exactPrice);
397583
this.updatePrice(newPrice);
398584
}
399585
}
@@ -508,27 +694,34 @@ export class ListItemModal extends FloatElement {
508694
? `$${(this.recommendedPrice / 100).toFixed(2)}`
509695
: 'N/A'}
510696
</label>
511-
<input
512-
type="number"
513-
step="0.01"
514-
min="0"
515-
max="100000"
516-
class="price-input"
517-
.value="${this.customPrice ? (this.customPrice / 100).toFixed(2) : ''}"
518-
@input="${this.handlePriceChange}"
519-
placeholder="${this.listingType === 'buy_now'
520-
? 'Enter listing price in USD (max $100,000)'
521-
: 'Enter starting price in USD (max $100,000)'}"
522-
/>
697+
<div class="price-input-container">
698+
<span class="price-input-prefix">$</span>
699+
<input
700+
type="text"
701+
inputmode="decimal"
702+
class="price-input"
703+
.value="${this.customPrice ? this.formatInputPrice(this.customPrice) : ''}"
704+
@input="${this.handlePriceChange}"
705+
placeholder="${this.listingType === 'buy_now'
706+
? 'Enter listing price in USD (max $100,000)'
707+
: 'Enter starting price in USD (max $100,000)'}"
708+
/>
709+
</div>
523710
<input
524711
type="range"
525712
min="80"
526713
max="120"
714+
step="0.1"
527715
.value="${this.pricePercentage}"
528716
@input="${this.handlePercentageChange}"
529717
class="percentage-slider"
530718
/>
531-
<div>Percentage of recommended price: ${this.pricePercentage.toFixed(0)}%</div>
719+
<div>
720+
Percentage of recommended price:
721+
${this.recommendedPrice && this.customPrice
722+
? Math.round((this.customPrice / this.recommendedPrice) * 100)
723+
: 100}%
724+
</div>
532725
533726
${this.listingType === 'auction'
534727
? html`
@@ -561,6 +754,28 @@ export class ListItemModal extends FloatElement {
561754
</div>
562755
563756
${this.error ? html`<div class="error-message">${this.error}</div>` : ''}
757+
${this.customPrice
758+
? html`
759+
<div class="price-breakdown">
760+
<div class="price-breakdown-row">
761+
<span>Subtotal</span>
762+
<span>$${this.formatPrice(this.customPrice)}</span>
763+
</div>
764+
<div class="price-breakdown-row">
765+
<span>Sale Fee (2%)</span>
766+
<span>-$${this.formatPrice(this.getSaleFee(this.customPrice))}</span>
767+
</div>
768+
<div class="price-breakdown-row">
769+
<span>Total Earnings</span>
770+
<span
771+
>$${this.formatPrice(
772+
this.customPrice - this.getSaleFee(this.customPrice)
773+
)}</span
774+
>
775+
</div>
776+
</div>
777+
`
778+
: ''}
564779
565780
<button
566781
class="submit-button"

0 commit comments

Comments
 (0)