mirror of
https://github.com/KevinMidboe/planetposen-frontend.git
synced 2025-10-29 13:10:12 +00:00
Displays payment, shipping, errors page & edit and add shipment
This commit is contained in:
202
src/lib/components/ShipmentProgress.svelte
Normal file
202
src/lib/components/ShipmentProgress.svelte
Normal file
@@ -0,0 +1,202 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Time from './Time.svelte';
|
||||
import CircleLoading from './loading/CircleLoading.svelte';
|
||||
import { buildApiUrl } from '$lib/utils/apiUrl';
|
||||
import type { IShipmentResponse } from '$lib/interfaces/IShipping';
|
||||
|
||||
export let shipment: IShipmentResponse;
|
||||
|
||||
interface TrackingEvent {
|
||||
city: string;
|
||||
consignmentEvent: boolean;
|
||||
country: string;
|
||||
countryCode: string;
|
||||
date: string;
|
||||
dateIso: Date;
|
||||
description: string;
|
||||
displayDate: string;
|
||||
displayTime: string;
|
||||
gpsMapUrl: string;
|
||||
gpsXCoordinate: string;
|
||||
gpsYCoordinate: string;
|
||||
insignificant: boolean;
|
||||
lmCauseCode: string;
|
||||
lmEventCode: string;
|
||||
lmMeasureCode: string;
|
||||
postalCode: string;
|
||||
recipientSignature: null;
|
||||
status: string;
|
||||
unitId: string;
|
||||
unitInformationUrl: null;
|
||||
unitType: string;
|
||||
}
|
||||
|
||||
let trackedShipment: TrackingEvent[] | null = null;
|
||||
|
||||
async function getTracking() {
|
||||
const url = buildApiUrl(`/api/v1/shipment/${shipment?.shipment_id}/track`);
|
||||
try {
|
||||
trackedShipment = null;
|
||||
const trackingResponse = await fetch(url).then((resp) => resp.json());
|
||||
|
||||
trackedShipment = trackingResponse?.shipment?.events || [];
|
||||
} catch (error) {
|
||||
console.log('api error from track:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!shipment) return;
|
||||
getTracking();
|
||||
});
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<h2>Shipment history</h2>
|
||||
|
||||
{#if trackedShipment === null}
|
||||
<div class="loading-content">
|
||||
<CircleLoading />
|
||||
</div>
|
||||
{:else if trackedShipment?.length > 0}
|
||||
<ul class="tracking">
|
||||
{#each trackedShipment as event}
|
||||
<li>
|
||||
<span class="indicator"></span>
|
||||
<div class="details">
|
||||
<span class="message">{event.description}</span>
|
||||
|
||||
{#if event?.status}
|
||||
<div>
|
||||
<span>{event.status} - </span>
|
||||
<span style="text-transform: capitalize;"><Time time="{event.date}" /></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="location">
|
||||
{#if event.city.length > 0}
|
||||
<span>{event?.city?.toLowerCase()}</span>
|
||||
{/if}
|
||||
|
||||
{#if event.countryCode.length > 0}
|
||||
<span data-separator=","
|
||||
>{event.city.length > 0 ? event?.countryCode : event?.country}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<div>
|
||||
<h3>Error! Unable to retrieve shipment history</h3>
|
||||
<p>
|
||||
Try again later or contact <a class="link" href="mailto:support@planet.schleppe.cloud"
|
||||
>support@planet.schleppe.cloud</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../styles/media-queries.scss';
|
||||
@import '../../styles/effects.scss';
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.tracking {
|
||||
padding-left: 1.5rem;
|
||||
|
||||
@include mobile {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
margin: 1rem 0.3rem;
|
||||
padding: 0.8rem 0.3rem;
|
||||
position: relative;
|
||||
|
||||
&:first-of-type .indicator {
|
||||
@include pulse-dot;
|
||||
&::after {
|
||||
left: calc(0.6rem + 5.5px);
|
||||
}
|
||||
|
||||
&::before {
|
||||
height: 100%;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type .indicator::before {
|
||||
height: 100%;
|
||||
top: -50%;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
display: block;
|
||||
align-self: center;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
background-color: #e32d22;
|
||||
border-radius: 50%;
|
||||
margin-right: 2rem;
|
||||
|
||||
@include mobile {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: calc(0.3rem + 8.5px);
|
||||
height: calc(100% + 1rem);
|
||||
width: 3px;
|
||||
background-color: #e32d22;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.message {
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.location {
|
||||
text-transform: capitalize;
|
||||
|
||||
span:nth-of-type(2) {
|
||||
position: relative;
|
||||
margin-left: 3px;
|
||||
padding-left: 3px;
|
||||
|
||||
&::before {
|
||||
content: attr(data-separator) ' ';
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
width: 100%;
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
25
src/lib/components/Time.svelte
Normal file
25
src/lib/components/Time.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
export let time: Date | string | undefined;
|
||||
|
||||
let dateLocaleString: string;
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
timeZone: 'Europe/Oslo'
|
||||
};
|
||||
|
||||
if (time) {
|
||||
dateLocaleString = new Date(time).toLocaleString('no-NB', options);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if dateLocaleString}
|
||||
<span>{dateLocaleString}</span>
|
||||
{:else}
|
||||
<span>(no date found)</span>
|
||||
{/if}
|
||||
42
src/lib/interfaces/IShipping.ts
Normal file
42
src/lib/interfaces/IShipping.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export interface IShippingCourier {
|
||||
id: string;
|
||||
name: string;
|
||||
website: string;
|
||||
has_api: boolean;
|
||||
}
|
||||
|
||||
export interface IShipment {
|
||||
id: string;
|
||||
courier_id: string;
|
||||
tracking_code: string;
|
||||
tracking_link: string;
|
||||
}
|
||||
|
||||
export interface IShipmentEvent {
|
||||
event_id: string;
|
||||
shipment_id: string;
|
||||
description: string;
|
||||
status: string;
|
||||
location: string;
|
||||
event_time: Date;
|
||||
updated: Date;
|
||||
created: Date;
|
||||
}
|
||||
|
||||
export interface IShipmentResponse {
|
||||
shipment_id: string
|
||||
order_id: string
|
||||
courier: string
|
||||
has_api: boolean
|
||||
courier_id: number
|
||||
tracking_code: string
|
||||
tracking_link: string
|
||||
user_notified: boolean
|
||||
}
|
||||
|
||||
export interface ICourier {
|
||||
courier_id: number
|
||||
name: string
|
||||
website: string
|
||||
has_api: boolean
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch }) => {
|
||||
const res = await fetch('/api/orders');
|
||||
const res = await fetch('/api/v1/orders');
|
||||
const response = await res.json();
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script lang="ts">
|
||||
import OrdersTable from './OrdersTable.svelte';
|
||||
import BadgeType from '$lib/interfaces/BadgeType';
|
||||
import PageMeta from '$lib/components/PageMeta.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import type { IOrder } from '$lib/interfaces/IOrder';
|
||||
import type { IOrderSummary } from '$lib/interfaces/IOrder';
|
||||
|
||||
export let data: PageData;
|
||||
const orders = data.orders as Array<IOrder>;
|
||||
const orders = data.orders as Array<IOrderSummary>;
|
||||
|
||||
const pendingOrders = orders.filter(
|
||||
const successfulOrders = orders.filter((el) => el.status === 'CONFIRMED');
|
||||
const incompleteOrders = orders.filter(
|
||||
(el) => el.status === BadgeType.INFO || el.status === 'INITIATED'
|
||||
);
|
||||
const inTransitOrders = orders.filter((el) => el.status === BadgeType.PENDING);
|
||||
@@ -17,38 +19,25 @@
|
||||
el.status !== BadgeType.PENDING &&
|
||||
el.status !== BadgeType.INFO &&
|
||||
el.status !== 'INITIATED' &&
|
||||
el.status !== 'CONFIRMED' &&
|
||||
el.status !== BadgeType.WARNING
|
||||
);
|
||||
|
||||
const deliveredOrders: Array<IOrder> = [];
|
||||
const deliveredOrders: Array<IOrderSummary> = [];
|
||||
</script>
|
||||
|
||||
<PageMeta title="Orders" description="View all webshop orders" />
|
||||
<div class="page">
|
||||
<h1>Orders</h1>
|
||||
<section class="content">
|
||||
{#if attentionOrders?.length}
|
||||
<h2>⚠️ orders needing attention</h2>
|
||||
|
||||
<OrdersTable orders="{attentionOrders}" />
|
||||
<OrdersTable title="⚠️ orders needing attention" orders="{attentionOrders}" />
|
||||
{/if}
|
||||
|
||||
<h2>📬 pending orders</h2>
|
||||
<OrdersTable orders="{pendingOrders}" />
|
||||
|
||||
<h2>📦 in transit</h2>
|
||||
<OrdersTable orders="{inTransitOrders}" />
|
||||
|
||||
<h2>🙅♀️ cancelled/returns</h2>
|
||||
<OrdersTable orders="{otherOrders}" />
|
||||
|
||||
<h2>🏠🎁 delivered orders</h2>
|
||||
<OrdersTable orders="{deliveredOrders}" />
|
||||
<OrdersTable title="📬 purchased orders" orders="{successfulOrders}" />
|
||||
<OrdersTable title="📦 in transit" orders="{inTransitOrders}" />
|
||||
<OrdersTable title="🙅♀️ cancelled/returns" orders="{otherOrders}" />
|
||||
<OrdersTable title="💤 incomplete orders" orders="{incompleteOrders}" />
|
||||
<OrdersTable title="🎁🏠 delivered orders" orders="{deliveredOrders}" />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style lang="scss" module="scoped">
|
||||
section.content h2 {
|
||||
// text-decoration: underline;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import Badge from '$lib/components/Badge.svelte';
|
||||
import Time from '$lib/components/Time.svelte';
|
||||
import type { IOrderSummary } from '$lib/interfaces/IOrder';
|
||||
|
||||
export let title: string;
|
||||
export let orders: Array<IOrderSummary>;
|
||||
|
||||
function navigate(order: IOrderSummary) {
|
||||
@@ -10,15 +12,16 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<h2>{title} <span class="section-count">{orders?.length || 0}</span></h2>
|
||||
{#if orders?.length}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Amount</th>
|
||||
<th>Status</th>
|
||||
<th>Order ID</th>
|
||||
<th>Customer</th>
|
||||
<th>Date</th>
|
||||
<th>Order ID</th>
|
||||
<th>Receipt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -29,18 +32,12 @@
|
||||
<td>NOK {order.order_sum}</td>
|
||||
|
||||
<td>
|
||||
<Badge title="{order.status}" type="{order?.status?.type}" />
|
||||
<Badge title="{order.status}" />
|
||||
</td>
|
||||
|
||||
<td>{order.order_id}</td>
|
||||
<td>{order.first_name} {order.last_name}</td>
|
||||
<td
|
||||
>{order?.created
|
||||
? new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
|
||||
new Date(order.created)
|
||||
)
|
||||
: ''}</td
|
||||
>
|
||||
<td><Time time="{order?.created}" /></td>
|
||||
<td>{order.order_id}</td>
|
||||
<td>
|
||||
<a href="receipt/{order.order_id}?email={order.email}">🧾</a>
|
||||
</td>
|
||||
@@ -56,11 +53,10 @@
|
||||
@import '../../styles/media-queries.scss';
|
||||
|
||||
h2 {
|
||||
// text-decoration: underline;
|
||||
font-size: 1.2rem;
|
||||
|
||||
.section-count {
|
||||
background-color: rgba(0,0,0,0.15);
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
padding: 0.3rem 0.4rem;
|
||||
margin-left: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
@@ -105,8 +101,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
th:last-of-type,
|
||||
td:last-of-type {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
tr > *:first-child {
|
||||
tr > *:nth-child(4),
|
||||
tr > *:nth-child(5) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
41
src/routes/orders/[id]/+error.svelte
Normal file
41
src/routes/orders/[id]/+error.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
|
||||
console.log('page data:', $page.error);
|
||||
|
||||
$: parsedApiResponse = JSON.stringify($page.error?.apiResponse || {}, null, 4);
|
||||
</script>
|
||||
|
||||
<h1>Oisann! Klarte ikke hente order</h1>
|
||||
|
||||
<p>
|
||||
Det du søkte etter fantes ikke her. Om du tror dette er en feil, ta kontakt <a
|
||||
class="link"
|
||||
href="mailto:support@planetposen.no">support@planetposen.no</a
|
||||
> for spørsmål.
|
||||
</p>
|
||||
<div class="error">
|
||||
<span>Internal error message:</span>
|
||||
<pre>
|
||||
<code>{parsedApiResponse}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.error {
|
||||
margin-top: 4rem;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
font-size: 1.4rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
border-bottom: 2px solid var(--color-theme-1);
|
||||
}
|
||||
|
||||
code {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,21 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { IOrderDTO } from '$lib/interfaces/ApiResponse';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch, params }) => {
|
||||
const { id } = params;
|
||||
|
||||
const res = await fetch(`/api/order/${id}`);
|
||||
const orderResponse: IOrderDTO = await res.json();
|
||||
const res = await fetch(`/api/v1/order/${id}`);
|
||||
const orderResponse = await res.json();
|
||||
|
||||
if (orderResponse?.success == false || orderResponse?.order === undefined) {
|
||||
throw Error(':(');
|
||||
console.log('throwing error', orderResponse);
|
||||
|
||||
throw error(404, {
|
||||
apiResponse: orderResponse,
|
||||
message: 'Something went wrong! Unable to get order'
|
||||
});
|
||||
}
|
||||
|
||||
return { order: orderResponse.order };
|
||||
return { order: orderResponse?.order };
|
||||
};
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
import PaymentDetails from './PaymentDetails.svelte';
|
||||
import CustomerDetails from './CustomerDetails.svelte';
|
||||
import TrackingDetails from './TrackingDetails.svelte';
|
||||
import ShipmentProgress from '$lib/components/ShipmentProgress.svelte';
|
||||
import type { IOrder } from '$lib/interfaces/IOrder';
|
||||
import type { PageServerData } from './$types';
|
||||
|
||||
export let data: PageServerData;
|
||||
let order = data.order as IOrder;
|
||||
console.log('order:', order);
|
||||
|
||||
function orderSubTotal() {
|
||||
if (!order || order.lineItems?.length === 0) return;
|
||||
if (!order || order?.lineItems?.length === 0) return;
|
||||
|
||||
let sum = 0;
|
||||
order.lineItems.forEach((lineItem) => (sum = sum + lineItem.quantity * lineItem.price));
|
||||
@@ -21,20 +21,21 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1>Order: {order.orderid}</h1>
|
||||
<h1>Order id: {order?.orderid}</h1>
|
||||
<div class="order">
|
||||
<!-- <p>Order: {JSON.stringify(order)}</p> -->
|
||||
<h2 class="price"><span class="amount">{orderSubTotal()}.00</span> Nok</h2>
|
||||
|
||||
<OrderSummary order="{order}" />
|
||||
<OrderProducts lineItems="{order?.lineItems}" />
|
||||
|
||||
<PaymentDetails order="{order}" />
|
||||
<PaymentDetails payment="{order?.payment}" />
|
||||
<CustomerDetails customer="{order?.customer}" />
|
||||
<TrackingDetails shipping="{order?.shipping}" />
|
||||
<TrackingDetails shipping="{order?.shipping}" orderId="{order?.orderid}" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../../styles/media-queries.scss';
|
||||
|
||||
h2.price {
|
||||
font-size: 1.5rem;
|
||||
color: grey;
|
||||
|
||||
@@ -67,19 +67,20 @@
|
||||
.image-column {
|
||||
width: 4rem;
|
||||
max-width: 4rem;
|
||||
margin: 0 0.5rem;
|
||||
|
||||
@include desktop {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
white-space: nowrap;
|
||||
padding: 0.4rem 0.6rem;
|
||||
}
|
||||
|
||||
tbody {
|
||||
img {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
@@ -102,11 +103,5 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// @include mobile {
|
||||
// tr > *:last-child, tr > :nth-child(4) {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
<script lang="ts">
|
||||
import Badge from '$lib/components/Badge.svelte';
|
||||
import Time from '$lib/components/Time.svelte';
|
||||
import type { IOrder } from '$lib/interfaces/IOrder';
|
||||
|
||||
export let order: IOrder;
|
||||
let paymentMethod: string = Math.random() > 0.5 ? 'Stripe' : 'ApplePay';
|
||||
</script>
|
||||
|
||||
<ul class="summary-list">
|
||||
<li>
|
||||
<span class="label">Status</span>
|
||||
<Badge title="{order.status}" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Last update</span>
|
||||
<span>{order.updated}</span>
|
||||
<Time time="{order.updated}" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Created</span>
|
||||
<Time time="{order.created}" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
@@ -18,12 +29,12 @@
|
||||
|
||||
<li>
|
||||
<span class="label">Receipt</span>
|
||||
<span><a href="/receipt/{order.orderid}">{order.orderid}</a></span>
|
||||
<a href="/receipt/{order.orderid}" class="link">{order.orderid}</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Payment method</span>
|
||||
<span>{paymentMethod}</span>
|
||||
<span style="text-transform: capitalize;">{order?.payment?.type}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -1,36 +1,94 @@
|
||||
<script lang="ts">
|
||||
import Badge from '$lib/components/Badge.svelte';
|
||||
// import type BadgeType from '$lib/interfaces/BadgeType';
|
||||
import type { IOrder } from '$lib/interfaces/IOrder';
|
||||
import Time from '$lib/components/Time.svelte';
|
||||
import type { IStripePayment } from '$lib/interfaces/IOrder';
|
||||
|
||||
export let order: IOrder;
|
||||
export let payment: IStripePayment;
|
||||
const initiatedAmount = payment?.amount ? payment.amount / 100 : 0;
|
||||
const capturedAmount = payment?.amount_captured ? payment.amount_captured / 100 : 0;
|
||||
const refundedAmount = payment?.amount_refunded ? payment.amount_refunded / 100 : 0;
|
||||
|
||||
function calculateApproximateFee() {
|
||||
if (!payment?.amount_captured) return null;
|
||||
|
||||
const percentCut = 0.024;
|
||||
const fixedCut = 2;
|
||||
|
||||
return capturedAmount * percentCut + fixedCut;
|
||||
}
|
||||
|
||||
function round(num: number) {
|
||||
return Math.round(num * 100) / 100;
|
||||
}
|
||||
|
||||
let stripeDashboardUrl = `https://dashboard.stripe.com/test/payments/${payment?.stripe_transaction_id}`;
|
||||
const fee = calculateApproximateFee();
|
||||
const net = fee ? capturedAmount - fee - refundedAmount : null;
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<h2>Payment details</h2>
|
||||
<ul class="property-list">
|
||||
<li>
|
||||
<span class="label">Amount</span>
|
||||
<span>{order?.payment?.amount}.10 kr</span>
|
||||
<span class="label">Stripe link</span>
|
||||
{#if payment?.stripe_transaction_id}
|
||||
<a href="{stripeDashboardUrl}" class="link" target="_blank" rel="noreferrer">
|
||||
<span>{payment?.stripe_transaction_id}</span>
|
||||
</a>
|
||||
{:else}
|
||||
<span>(No payment found)</span>
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Fee</span>
|
||||
<span>2.25 kr</span>
|
||||
<span class="label">Amount requested</span>
|
||||
<span>{initiatedAmount}.00 kr</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Amount charged</span>
|
||||
<span>{capturedAmount}.00 kr</span>
|
||||
</li>
|
||||
|
||||
{#if payment?.amount_refunded > 0}
|
||||
<li>
|
||||
<span class="label">Refunded</span>
|
||||
<span>{payment?.amount_refunded / 100}.00 kr</span>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
<li>
|
||||
<span class="label">Fee (approx)</span>
|
||||
<span>{fee === null ? '-' : round(fee) + ' kr'}</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Net</span>
|
||||
<span>7.85 kr</span>
|
||||
<span>{net === null ? '-' : round(net) + ' kr'}</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Status</span>
|
||||
<Badge title="{order?.status?.text || order.status}" type="{order.status.type}" />
|
||||
<Badge title="{payment?.stripe_status}" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Created</span>
|
||||
<Time time="{payment?.created}" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Updated</span>
|
||||
<Time time="{payment?.updated}" />
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
@import './styles-order-page.scss';
|
||||
|
||||
a.link {
|
||||
max-width: 60%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,35 +1,139 @@
|
||||
<script lang="ts">
|
||||
import type { IShipping } from '$lib/interfaces/IOrder';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import ShipmentProgress from '$lib/components/ShipmentProgress.svelte';
|
||||
import { buildApiUrl } from '$lib/utils/apiUrl';
|
||||
import type { IShipmentResponse, ICourier } from '$lib/interfaces/IShipping';
|
||||
|
||||
export let shipping: IShipping;
|
||||
export let shipping: IShipmentResponse;
|
||||
export let orderId: string;
|
||||
|
||||
function fetchCouriers() {
|
||||
if (couriers?.length > 0) return couriers;
|
||||
|
||||
const url = buildApiUrl('/api/v1/shipment/couriers');
|
||||
fetch(url)
|
||||
.then((resp) => resp.json())
|
||||
.then((response) => (couriers = response?.couriers || []));
|
||||
}
|
||||
|
||||
function updateShipment() {
|
||||
const url = buildApiUrl(`/api/v1/shipment/${shipping.shipment_id}`);
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
tracking_code: trackingCode,
|
||||
tracking_link: trackingLink,
|
||||
courier_id: selectedCourier
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
|
||||
fetch(url, options)
|
||||
.then((resp) => resp.json())
|
||||
.then((response) => shipping = response?.shipment || shipping);
|
||||
}
|
||||
|
||||
function addShipment() {
|
||||
const url = buildApiUrl(`/api/v1/shipment/${orderId}`);
|
||||
fetch(url, { method: 'POST' })
|
||||
.then((resp) => resp.json())
|
||||
.then((response) => {
|
||||
shipping = response?.shipment;
|
||||
toggleEdit();
|
||||
});
|
||||
}
|
||||
|
||||
function toggleEdit() {
|
||||
edit = !edit;
|
||||
if (edit) fetchCouriers();
|
||||
else updateShipment();
|
||||
}
|
||||
|
||||
let edit: boolean = false;
|
||||
let trackingCode: string = shipping?.tracking_code;
|
||||
let trackingLink: string = shipping?.tracking_link;
|
||||
let courier: string = shipping?.courier;
|
||||
let selectedCourier: any = shipping?.courier_id;
|
||||
let couriers: ICourier[];
|
||||
</script>
|
||||
|
||||
{#if shipping}
|
||||
<section>
|
||||
<h2>Tracking</h2>
|
||||
<section>
|
||||
<h2>Tracking</h2>
|
||||
|
||||
{#if shipping}
|
||||
<ul class="property-list">
|
||||
<li>
|
||||
<span class="label">Tracking code</span>
|
||||
<span>{shipping.tracking_code}</span>
|
||||
<span class="label" for="courier">Tracking company</span>
|
||||
{#if !edit}
|
||||
<span>{shipping.courier}</span>
|
||||
{:else if couriers?.length > 0}
|
||||
<select bind:value="{selectedCourier}" name="couriers" id="courier">
|
||||
<option value="">--Please choose an option--</option>
|
||||
{#each couriers as courier}
|
||||
<option value="{courier.courier_id}">{courier.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Tracking company</span>
|
||||
<span>{shipping.company}</span>
|
||||
<span class="label">Tracking code</span>
|
||||
{#if !edit}
|
||||
<span>{shipping.tracking_code}</span>
|
||||
{:else}
|
||||
<input bind:value="{trackingCode}" />
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="label">Link</span>
|
||||
<span
|
||||
><a href="{shipping.tracking_link}" target="_blank" rel="noopener noreferrer"
|
||||
>{shipping.tracking_link}</a
|
||||
></span
|
||||
>
|
||||
{#if !edit}
|
||||
<a href="{shipping.tracking_link}" class="link" target="_blank" rel="noopener noreferrer">
|
||||
{shipping.tracking_link}
|
||||
</a>
|
||||
{:else}
|
||||
<input style="margin-bottom: 0" class="wide" bind:value="{trackingLink}" />
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<div class="button-container">
|
||||
<Button text="{!edit ? 'Edit' : 'Save'}" on:click="{toggleEdit}" />
|
||||
</div>
|
||||
|
||||
{#if shipping?.tracking_code && shipping?.has_api}
|
||||
{#key shipping.tracking_code}
|
||||
<ShipmentProgress shipment="{shipping}" />
|
||||
{/key}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if !shipping}
|
||||
<div class="button-container">
|
||||
<Button text="Add" on:click="{addShipment}" />
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
@import './styles-order-page.scss';
|
||||
|
||||
a {
|
||||
max-width: 60%;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
select {
|
||||
margin: -1px 0;
|
||||
}
|
||||
|
||||
input {
|
||||
margin: -2px 0 -2px -2px;
|
||||
padding: 0;
|
||||
|
||||
&.wide {
|
||||
width: -webkit-fill-available;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,8 +16,7 @@ section {
|
||||
}
|
||||
}
|
||||
|
||||
.label,
|
||||
.empty {
|
||||
.label {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
@@ -51,7 +50,6 @@ ul.property-list {
|
||||
|
||||
li span:last-of-type {
|
||||
@include mobile {
|
||||
min-width: 60%;
|
||||
white-space: normal;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user