Skip to content

Commit c882823

Browse files
author
Javed Hussain
committed
Refactored codebase and added Dockerfile with github actions
1 parent 7b8ab55 commit c882823

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1990
-2701
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Docker Publish
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
build_and_publish:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
packages: write
14+
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v3
18+
19+
- name: Set up Docker Buildx
20+
uses: docker/setup-buildx-action@v2
21+
22+
- name: Log in to GitHub Container Registry
23+
uses: docker/login-action@v2
24+
with:
25+
registry: ghcr.io
26+
username: ${{ github.actor }}
27+
password: ${{ secrets.GITHUB_TOKEN }}
28+
29+
- name: Build and push
30+
uses: docker/build-push-action@v4
31+
with:
32+
context: .
33+
push: true
34+
tags: ghcr.io/${{ github.repository }}:latest

Dockerfile

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Stage 1: Build Client
2+
FROM node:20-alpine AS client-builder
3+
4+
WORKDIR /usr/src/app/client
5+
6+
COPY client/package*.json ./
7+
RUN npm install
8+
COPY client/ ./
9+
10+
# Set the public API base URL during the build
11+
ARG PUBLIC_API_BASE_URL=/
12+
ENV PUBLIC_API_BASE_URL=$PUBLIC_API_BASE_URL
13+
14+
RUN npm run build
15+
16+
# Stage 2: Build Server
17+
FROM node:20-alpine AS server-builder
18+
19+
WORKDIR /usr/src/app/server
20+
21+
COPY server/package*.json ./
22+
RUN npm install
23+
COPY server/ ./
24+
RUN npm run build
25+
26+
# Stage 3: Final Production Image
27+
FROM node:20-alpine
28+
29+
WORKDIR /usr/src/app
30+
31+
# Install server production dependencies
32+
COPY --from=server-builder /usr/src/app/server/package*.json ./
33+
RUN npm install --omit=dev
34+
35+
# Copy built server code
36+
COPY --from=server-builder /usr/src/app/server/dist ./dist
37+
38+
# Copy client build output to a public directory
39+
COPY --from=client-builder /usr/src/app/client/build ./public
40+
41+
# Copy database and environment files
42+
COPY server/vehicles.db ./
43+
COPY server/.env ./
44+
45+
EXPOSE 3000
46+
47+
CMD ["node", "dist/index.js"]
Lines changed: 110 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,173 +1,121 @@
11
<script lang="ts">
2-
import { createEventDispatcher } from 'svelte';
3-
import FormField from '../Common/FormField.svelte';
4-
import { Calendar1, Gauge, DollarSign, Fuel, FileText } from '@lucide/svelte';
5-
import { env } from '$env/dynamic/public';
2+
import { createEventDispatcher } from "svelte";
3+
import FuelRefillFormComponent from "./FuelRefillFormComponent.svelte";
4+
import { env } from "$env/dynamic/public";
65
7-
const dispatch = createEventDispatcher();
6+
const dispatch = createEventDispatcher();
87
9-
export let vehicleId: number | null = null;
10-
export let onSuccess: (() => void) | null = null;
11-
export let showModal: boolean = false;
12-
export let closeModal: (() => void) | null = null;
8+
export let vehicleId: number | null = null;
9+
export let onSuccess: (() => void) | null = null;
10+
export let showModal: boolean = false;
11+
export let closeModal: (() => void) | null = null;
1312
14-
let refill = {
15-
date: '',
16-
odometer: '',
17-
fuelAmount: '',
18-
cost: '',
19-
notes: ''
20-
};
13+
let refill = {
14+
date: "",
15+
odometer: "",
16+
fuelAmount: "",
17+
cost: "",
18+
notes: "",
19+
};
2120
22-
let loading = false;
23-
let error = '';
21+
let loading = false;
22+
let error = "";
2423
25-
function resetForm() {
26-
refill = {
27-
date: '',
28-
odometer: '',
29-
fuelAmount: '',
30-
cost: '',
31-
notes: ''
32-
};
33-
}
24+
function resetForm() {
25+
refill = {
26+
date: "",
27+
odometer: "",
28+
fuelAmount: "",
29+
cost: "",
30+
notes: "",
31+
};
32+
}
3433
35-
async function handleSubmit(e: Event) {
36-
e.preventDefault();
37-
error = '';
38-
if (!vehicleId) {
39-
error = 'No vehicle selected.';
40-
return;
41-
}
42-
if (!refill.date || !refill.odometer || !refill.fuelAmount || !refill.cost) {
43-
error = 'Please fill in all required fields.';
44-
return;
45-
}
46-
loading = true;
47-
try {
48-
const response = await fetch(`${env.PUBLIC_API_BASE_URL}/api/vehicles/${vehicleId}/fuel-logs`, {
49-
method: 'POST',
50-
headers: {
51-
'Content-Type': 'application/json',
52-
'X-User-PIN': localStorage.getItem('userPin') || ''
53-
},
54-
body: JSON.stringify({
55-
date: refill.date,
56-
odometer: parseFloat(refill.odometer),
57-
fuelAmount: parseFloat(refill.fuelAmount),
58-
cost: parseFloat(refill.cost),
59-
notes: refill.notes
60-
})
61-
});
62-
if (response.ok) {
63-
resetForm();
64-
dispatch('success');
65-
if (onSuccess) onSuccess();
66-
} else {
67-
const data = await response.json();
68-
error = data?.message || 'Failed to log fuel refill.';
69-
}
70-
} catch (err) {
71-
error = 'Network error. Please try again.';
72-
} finally {
73-
loading = false;
74-
}
75-
}
34+
async function handleSubmit() {
35+
error = "";
36+
if (!vehicleId) {
37+
error = "No vehicle selected.";
38+
return;
39+
}
40+
if (!refill.date || !refill.odometer || !refill.fuelAmount || !refill.cost) {
41+
error = "Please fill in all required fields.";
42+
return;
43+
}
44+
loading = true;
45+
try {
46+
const response = await fetch(
47+
`${env.PUBLIC_API_BASE_URL}/api/vehicles/${vehicleId}/fuel-logs`,
48+
{
49+
method: "POST",
50+
headers: {
51+
"Content-Type": "application/json",
52+
"X-User-PIN": localStorage.getItem("userPin") || "",
53+
},
54+
body: JSON.stringify({
55+
date: refill.date,
56+
odometer: parseFloat(refill.odometer),
57+
fuelAmount: parseFloat(refill.fuelAmount),
58+
cost: parseFloat(refill.cost),
59+
notes: refill.notes,
60+
}),
61+
}
62+
);
63+
if (response.ok) {
64+
resetForm();
65+
dispatch("success");
66+
if (onSuccess) onSuccess();
67+
} else {
68+
const data = await response.json();
69+
error = data?.message || "Failed to log fuel refill.";
70+
}
71+
} catch (err) {
72+
error = "Network error. Please try again.";
73+
} finally {
74+
loading = false;
75+
}
76+
}
7677
</script>
7778

7879
{#if showModal}
79-
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
80-
<div
81-
class="animate-scale-up relative max-h-[90vh] w-full max-w-xl overflow-y-auto rounded-2xl bg-white p-10 shadow-2xl dark:bg-gray-800"
82-
>
83-
<!-- Close Icon -->
84-
<button
85-
type="button"
86-
class="absolute top-6 right-6 rounded-full bg-gray-100 p-2 text-gray-400 transition hover:text-gray-700 dark:bg-gray-700 dark:text-gray-300 dark:hover:text-white"
87-
on:click={closeModal}
88-
aria-label="Close"
89-
>
90-
<svg
91-
xmlns="http://www.w3.org/2000/svg"
92-
class="h-6 w-6"
93-
fill="none"
94-
viewBox="0 0 24 24"
95-
stroke="currentColor"
96-
>
97-
<path
98-
stroke-linecap="round"
99-
stroke-linejoin="round"
100-
stroke-width="2"
101-
d="M6 18L18 6M6 6l12 12"
102-
/>
103-
</svg>
104-
</button>
105-
<h2 class="mb-2 text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100">
106-
Log Fuel Refill
107-
</h2>
108-
<div class="mb-8 border-b border-gray-200 dark:border-gray-700"></div>
109-
<form class="space-y-6" on:submit={handleSubmit} aria-labelledby="fuel-refill-form-title">
110-
<FormField
111-
id="date"
112-
type="date"
113-
placeholder="Date"
114-
bind:value={refill.date}
115-
icon={Calendar1}
116-
required={true}
117-
ariaLabel="Refill Date"
118-
/>
119-
<FormField
120-
id="odometer"
121-
type="number"
122-
placeholder="Odometer Reading"
123-
bind:value={refill.odometer}
124-
icon={Gauge}
125-
required={true}
126-
ariaLabel="Odometer Reading"
127-
inputClass="step-0.01"
128-
/>
129-
<FormField
130-
id="fuelAmount"
131-
type="number"
132-
placeholder="Fuel Amount (liters)"
133-
bind:value={refill.fuelAmount}
134-
icon={Fuel}
135-
required={true}
136-
ariaLabel="Fuel Amount"
137-
inputClass="step-0.01"
138-
/>
139-
<FormField
140-
id="cost"
141-
type="number"
142-
placeholder="Cost"
143-
bind:value={refill.cost}
144-
icon={DollarSign}
145-
required={true}
146-
ariaLabel="Fuel Cost"
147-
inputClass="step-0.01"
148-
/>
149-
<FormField
150-
id="notes"
151-
type="text"
152-
placeholder="Notes (optional)"
153-
bind:value={refill.notes}
154-
icon={FileText}
155-
ariaLabel="Notes"
156-
/>
157-
{#if error}
158-
<div class="text-sm text-red-600 dark:text-red-400" role="alert">{error}</div>
159-
{/if}
160-
<div class="flex justify-center">
161-
<button
162-
type="submit"
163-
class="flex cursor-pointer gap-2 rounded-lg border-2 bg-blue-600 px-3 py-1 text-lg font-semibold text-blue-600 shadow-md dark:text-blue-200"
164-
disabled={loading}
165-
aria-busy={loading}
166-
>
167-
{loading ? 'Logging...' : 'Log Refill'}
168-
</button>
169-
</div>
170-
</form>
171-
</div>
172-
</div>
80+
<div
81+
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
82+
>
83+
<div
84+
class="animate-scale-up relative max-h-[90vh] w-full max-w-xl overflow-y-auto rounded-2xl bg-white p-10 shadow-2xl dark:bg-gray-800"
85+
>
86+
<button
87+
type="button"
88+
class="absolute top-6 right-6 rounded-full bg-gray-100 p-2 text-gray-400 transition hover:text-gray-700 dark:bg-gray-700 dark:text-gray-300 dark:hover:text-white"
89+
on:click={closeModal}
90+
aria-label="Close"
91+
>
92+
<svg
93+
xmlns="http://www.w3.org/2000/svg"
94+
class="h-6 w-6"
95+
fill="none"
96+
viewBox="0 0 24 24"
97+
stroke="currentColor"
98+
>
99+
<path
100+
stroke-linecap="round"
101+
stroke-linejoin="round"
102+
stroke-width="2"
103+
d="M6 18L18 6M6 6l12 12"
104+
/>
105+
</svg>
106+
</button>
107+
<h2
108+
class="mb-2 text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100"
109+
>
110+
Log Fuel Refill
111+
</h2>
112+
<div class="mb-8 border-b border-gray-200 dark:border-gray-700"></div>
113+
<FuelRefillFormComponent
114+
bind:refill
115+
onSubmit={handleSubmit}
116+
bind:error
117+
bind:loading
118+
/>
119+
</div>
120+
</div>
173121
{/if}

0 commit comments

Comments
 (0)