Squashed commit of the following:
commit 6304df307b853f9a30d06307a99a77bc3b0392f4 Author: Ivan Dimitrov <ivan@idimitrov.dev> Date: Wed Nov 15 23:16:35 2023 +0200 parcel lab case commit 3e0a3648ab998e4754427da08aa9c539f043f3b4 Author: Ivan Dimitrov <ivan@idimitrov.dev> Date: Wed Nov 15 19:46:28 2023 +0200 some more content commit e91a56dac8c1ed0f50964265406b329621f712f3 Author: Ivan Dimitrov <ivan@idimitrov.dev> Date: Wed Nov 15 18:49:21 2023 +0200 styles
This commit is contained in:
parent
57218bd8e2
commit
a3d71f7b42
198
_content/cases/parcellab.md
Normal file
198
_content/cases/parcellab.md
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
---
|
||||||
|
title: Parcel Lab integration into international eCommerce app
|
||||||
|
goal: Integrate the Parcel Lab Post-purchase solution into all US and European WOSG websites
|
||||||
|
role: Plan, design and implement the integration
|
||||||
|
date: 2022 - 2023
|
||||||
|
z: 2
|
||||||
|
draft: false
|
||||||
|
---
|
||||||
|
|
||||||
|
> parcelLab is the only truly global enterprise post-purchase software provider, enabling brands to increase top-line revenue, decrease operational costs, and optimize the customer experience.
|
||||||
|
|
||||||
|
|
||||||
|
[Parcel Lab](https://parcellab.com/)
|
||||||
|
|
||||||
|
|
||||||
|
Parcel lab takes care of the post-purchase operations like order tracking, email notifications, delivery status updates, data processing and more so that businesses don't have to.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Technical overview
|
||||||
|
|
||||||
|
This integration is straightforward thanks to the [amazing documentation](https://how.parcellab.works/docs/) provided by the Parcel Lab team.
|
||||||
|
|
||||||
|
You really want to use the API even though there's more options to submit data to their platform.
|
||||||
|
|
||||||
|
The data model is based on the [tracking](https://how.parcellab.works/docs/onboarding/data-model) - a data object having four fields for the delivery information. An order is composed of one or more trackings.
|
||||||
|
|
||||||
|
Once data is submitted, the platform starts an automated process where it groups the new trackings to their respective orders and starts listening for events like
|
||||||
|
"dispatch", "payment received" etc. to run custom actions. Each business can configure these events and actions so that they best match their operations.
|
||||||
|
For example an "order created" event could notify the customer that the order has started as well as deal with some other business logic in the background.
|
||||||
|
|
||||||
|
Their [order status page](https://how.parcellab.works/docs/track-and-communicate/order-status-page) is a convenient script that you can configure for your website.
|
||||||
|
The script reads the URL to find an order number so that it can fetch the most up-to-date information for that order and display it in an iFrame.
|
||||||
|
|
||||||
|
This system allows for a seamless, declarative event-based integration where the business takes care of the data and events (and sales) and parcelLab takes care of the rest.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ParcelLab API details
|
||||||
|
|
||||||
|
Create the first order when a customer places it.
|
||||||
|
|
||||||
|
```hurl
|
||||||
|
POST https://api.parcellab.com/order/ # This is the method and the endpoint
|
||||||
|
user: business-uid # Header
|
||||||
|
token: access-token # Header
|
||||||
|
Content-Type: application/json # Header
|
||||||
|
{ # Body starts here
|
||||||
|
"xid": "id-of-delivery-before-tracking-number",
|
||||||
|
"destination_country_iso3": "DEU",
|
||||||
|
"client": "your-client-id",
|
||||||
|
"orderNo": "order1", # This is the order ID
|
||||||
|
"recipient": "Max Mustermann, parcelLab GmbH",
|
||||||
|
"recipient_notification": "Max",
|
||||||
|
"email": "max-mustermann@abc.xyz",
|
||||||
|
"street": "Landwehrstr. 39",
|
||||||
|
"zip_code": "80336",
|
||||||
|
"city": "München",
|
||||||
|
"billing_info": {
|
||||||
|
"name": "Max",
|
||||||
|
"phone": "1234",
|
||||||
|
"street": "Nice St. 69",
|
||||||
|
"city": "Nice",
|
||||||
|
"zip_code": "69420",
|
||||||
|
"country_iso3": "DEU"
|
||||||
|
},
|
||||||
|
"articles": [
|
||||||
|
{
|
||||||
|
"articleNo": "sku", # required
|
||||||
|
"articleName": "Some Nice Thingy", # required
|
||||||
|
"articleCategory": "Nice", # optional
|
||||||
|
"articleUrl": "https://example.com/nice", # optional
|
||||||
|
"articleImageUrl":"https://example.com/nice.png", # optional
|
||||||
|
"quantity": 69, # optional
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"customFields": {
|
||||||
|
"someField": {
|
||||||
|
"anything": "in",
|
||||||
|
"here": [],
|
||||||
|
"nice": 69
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will reflect on the parcelLab dashboard.
|
||||||
|
|
||||||
|
Add a few trackings to that order once you get the delivery number from the courier.
|
||||||
|
|
||||||
|
```hurl
|
||||||
|
POST https://api.parcellab.com/track/
|
||||||
|
user: business-uid
|
||||||
|
token: access-token
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"tracking_number": "1",
|
||||||
|
"courier": "dhl-germany",
|
||||||
|
"zip_code": "12345",
|
||||||
|
"deliveryNo": "1",
|
||||||
|
"orderNo": "order1" # This references the above order
|
||||||
|
}
|
||||||
|
|
||||||
|
POST https://api.parcellab.com/track/
|
||||||
|
user: business-uid
|
||||||
|
token: access-token
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"tracking_number": "2",
|
||||||
|
"courier": "dhl-germany",
|
||||||
|
"zip_code": "12345",
|
||||||
|
"deliveryNo": "2",
|
||||||
|
"orderNo": "order1"
|
||||||
|
}
|
||||||
|
|
||||||
|
POST https://api.parcellab.com/track/
|
||||||
|
user: business-uid
|
||||||
|
token: access-token
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"tracking_number": "3",
|
||||||
|
"courier": "dhl-germany",
|
||||||
|
"zip_code": "12345",
|
||||||
|
"deliveryNo": "3",
|
||||||
|
"orderNo": "order1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The order now has 3 trackings. Parcel Lab integrates with many couriers to provide status updates.
|
||||||
|
|
||||||
|
Update the order for whatever reason.
|
||||||
|
|
||||||
|
```hurl
|
||||||
|
POST https://api.parcellab.com/order/
|
||||||
|
user: business-uid
|
||||||
|
token: access-token
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"orderNo": "order1",
|
||||||
|
"customFields": {
|
||||||
|
"customerPleased": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These updates can act as event notifications allowing for a declarative configuration.
|
||||||
|
|
||||||
|
```hurl
|
||||||
|
POST https://api.parcellab.com/order/
|
||||||
|
user: business-uid
|
||||||
|
token: access-token
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"orderNo": "order1",
|
||||||
|
"send_date": "now" # Gets notified that the order has been dispatched
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Complete shipment.
|
||||||
|
|
||||||
|
```hurl
|
||||||
|
POST https://api.parcellab.com/order/
|
||||||
|
user: business-uid
|
||||||
|
token: access-token
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"orderNo": "order1",
|
||||||
|
"complete": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
All this can be viewed on the tracking page embedded anywhere.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div id="parcellab-track-and-trace">
|
||||||
|
<img src="https://cdn.parcellab.com/img/loading-spinner-1.gif" alt="loading" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function plTrackAndTraceStart() {
|
||||||
|
window.parcelLabTrackAndTrace.initialize({
|
||||||
|
plUserId: TYPE_YOUR_USER_ID_HERE
|
||||||
|
});
|
||||||
|
var linkTag = document.createElement('link');
|
||||||
|
linkTag.rel = 'stylesheet';
|
||||||
|
linkTag.href = 'https://cdn.parcellab.com/css/v5/main.min.css';
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(linkTag);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script async onload="plTrackAndTraceStart()"
|
||||||
|
src="https://cdn.parcellab.com/js/v5/main.min.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
This shows a nice UI that can be [customized](https://how.parcellab.works/docs/track-and-communicate/order-status-page/configuration#additional-options).
|
||||||
|
|
@ -3,7 +3,7 @@ title: Multi-tenant knowledge base website based on Google APIs
|
|||||||
goal: Create a modern multi-tenant web app that lets users use their Google Drive as a knowledge base
|
goal: Create a modern multi-tenant web app that lets users use their Google Drive as a knowledge base
|
||||||
role: Design and implement the web app
|
role: Design and implement the web app
|
||||||
date: Jul 29, 2023 - Nov 5, 2023
|
date: Jul 29, 2023 - Nov 5, 2023
|
||||||
z: 5
|
z: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
This project aims to be a Google Drive frontend. It uses the Google APIs to fetch document data and display that data in a wiki-style web page.
|
This project aims to be a Google Drive frontend. It uses the Google APIs to fetch document data and display that data in a wiki-style web page.
|
||||||
|
@ -7,5 +7,8 @@ z: 1
|
|||||||
draft: true
|
draft: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
[Wells Fargo](https://www.wellsfargo.com/) is a US based international financial institution operating in 35 countries and serving over 70 million people worldwide. [Source](https://en.wikipedia.org/wiki/Wells_Fargo)
|
||||||
|
|
||||||
|
|
||||||
|
They provide an
|
||||||
|
|
||||||
|
13
_content/cases/wiki.hoi2bunker.com.md
Normal file
13
_content/cases/wiki.hoi2bunker.com.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: Static wiki website powered by Markdown
|
||||||
|
goal: Scrape an old wiki website and create a new one from the content
|
||||||
|
role: Scrape, design and implement the app
|
||||||
|
date: Jun 22, 2023 - Jun 27, 2023
|
||||||
|
z: 6
|
||||||
|
draft: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Repo: [github.com](https://github.com/hearts-of-iron-2/wiki)
|
||||||
|
|
||||||
|
This project aims to revamp an old, unmaintained wiki website and bring it back to life.
|
||||||
|
|
@ -16,12 +16,15 @@
|
|||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
nvim = ide.nvim.${system} {
|
nvim = ide.nvim.${system} {
|
||||||
plugins.lsp.servers = {
|
plugins = {
|
||||||
|
lsp.servers = {
|
||||||
html.enable = true;
|
html.enable = true;
|
||||||
tsserver.enable = true;
|
tsserver.enable = true;
|
||||||
jsonls.enable = true;
|
jsonls.enable = true;
|
||||||
tailwindcss.enable = true;
|
tailwindcss.enable = true;
|
||||||
};
|
};
|
||||||
|
cmp-spell.enable = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
coreutils-full
|
coreutils-full
|
||||||
|
@ -53,3 +53,7 @@
|
|||||||
@apply text-xl
|
@apply text-xl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md a {
|
||||||
|
@apply underline text-teal-200 hover:text-cyan-500
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import rehypeHighlight from "rehype-highlight";
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import CopyButton from "$components/copy-button";
|
import CopyButton from "$components/copy-button";
|
||||||
import { getText } from "@/app/lib/react";
|
import { getText } from "$lib/react";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
slug: string[]
|
slug: string[]
|
||||||
@ -95,7 +95,7 @@ export default function Content({ params }: Props) {
|
|||||||
{role()}
|
{role()}
|
||||||
{date()}
|
{date()}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full m-auto lg:w-3/4">
|
<div className="w-full m-auto lg:w-3/4 mt-10">
|
||||||
{ctnt()}
|
{ctnt()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getCases } from "../lib/content";
|
import { getCases } from "$lib/content";
|
||||||
|
|
||||||
const Cases = () =>
|
const Cases = () =>
|
||||||
<div className="p-20 w-3/4 mx-auto flex flex-col gap-4">
|
<div className="p-20 w-3/4 mx-auto flex flex-col gap-4">
|
||||||
{getCases().filter(c => !c.data.draft).sort(c => Number(c.data.z)).reverse().map((c) => c.data).map((d) =>
|
{getCases().filter(c => !c.data.draft).sort((a, b) => Number(b.data.z) - Number(a.data.z)).map((c) => c.data).map((d) =>
|
||||||
<div key={d.slug} className="w-full h-max flex justify-center rounded-lg border-2">
|
<div key={d.slug} className="w-full h-max flex justify-center rounded-lg border-2">
|
||||||
<Link className="btn flex flex-col w-full text-center" href={d.slug}>
|
<Link className="btn flex flex-col w-full text-center" href={d.slug}>
|
||||||
<span className="text-lg px-6">{d.title}</span>
|
<span className="text-lg px-6">{d.title}</span>
|
||||||
|
@ -5,6 +5,6 @@ export const getText = (node: ReactNode | any) => {
|
|||||||
if (!props) {
|
if (!props) {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
const c = props.children
|
const c = props.children || ""
|
||||||
return typeof c === "string" ? c : c.map(getText).join("")
|
return typeof c === "string" ? c : c.map(getText).join("")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user