Skip to content

Commit 5b9984e

Browse files
committed
chore: 🎉 first commit
0 parents  commit 5b9984e

11 files changed

+5949
-0
lines changed

.gitignore

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Nuxt dev/build outputs
2+
.output
3+
.data
4+
.nuxt
5+
.nitro
6+
.cache
7+
dist
8+
9+
# Node dependencies
10+
node_modules
11+
12+
# Logs
13+
logs
14+
*.log
15+
16+
# Misc
17+
.DS_Store
18+
.fleet
19+
.idea
20+
21+
# Local env files
22+
.env
23+
.env.*
24+
!.env.example

README.md

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Nuxt 3 Minimal Starter
2+
3+
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
4+
5+
## Setup
6+
7+
Make sure to install the dependencies:
8+
9+
```bash
10+
# npm
11+
npm install
12+
13+
# pnpm
14+
pnpm install
15+
16+
# yarn
17+
yarn install
18+
19+
# bun
20+
bun install
21+
```
22+
23+
## Development Server
24+
25+
Start the development server on `http://localhost:3000`:
26+
27+
```bash
28+
# npm
29+
npm run dev
30+
31+
# pnpm
32+
pnpm run dev
33+
34+
# yarn
35+
yarn dev
36+
37+
# bun
38+
bun run dev
39+
```
40+
41+
## Production
42+
43+
Build the application for production:
44+
45+
```bash
46+
# npm
47+
npm run build
48+
49+
# pnpm
50+
pnpm run build
51+
52+
# yarn
53+
yarn build
54+
55+
# bun
56+
bun run build
57+
```
58+
59+
Locally preview production build:
60+
61+
```bash
62+
# npm
63+
npm run preview
64+
65+
# pnpm
66+
pnpm run preview
67+
68+
# yarn
69+
yarn preview
70+
71+
# bun
72+
bun run preview
73+
```
74+
75+
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

app.vue

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<template>
2+
<div class="flex justify-center h-screen items-start bg-gray-100 pt-20">
3+
<div class="border p-8 w-96 bg-white flex flex-col gap-8">
4+
<h1 class="text-xl font-medium">Christmas greeting card maker</h1>
5+
<div class="border aspect-square banner-here relative" v-html="svg"></div>
6+
<form class="flex flex-col gap-4">
7+
<input type="text" v-model="form.name" class="px-4 py-1 border w-full" placeholder="Enter name" />
8+
<input type="text" v-model="form.greeting" class="px-4 py-1 border w-full" placeholder="Enter greeting" />
9+
<input type="file" accept=".png, .jpg, .jpeg" @change="handleFileChange" class="px-4 py-1 border w-full" />
10+
<small class="text-gray-400">Must be an image below 100kb, png, jpeg and jpg formats only.</small>
11+
<button class="bg-indigo-500 text-white px-4 py-2" @click.prevent="downloadSvgAsJpeg(svg)">Download</button>
12+
</form>
13+
</div>
14+
</div>
15+
</template>
16+
17+
<script setup lang="ts">
18+
import { createSSRApp } from "vue";
19+
import { renderToString } from "@vue/server-renderer";
20+
import { useLocalStorage, watchDebounced } from "@vueuse/core";
21+
import satori from "satori";
22+
import { html } from "satori-html";
23+
import ChristmasCard from "./components/ChristmasCard.vue";
24+
25+
const form = useLocalStorage("app-form", { name: "", greeting: "Merry Christmas", photo: '' });
26+
const svg = ref("");
27+
const fonts = ref([]);
28+
29+
onMounted(async () => {
30+
fonts.value = await loadFonts([{ name: "InstrumentSans", url: "/fonts/InstrumentSans-Regular.ttf" }]);
31+
refreshGraphics();
32+
});
33+
34+
watchDebounced(form, refreshGraphics, { deep: true, debounce: 500, maxWait: 1000 });
35+
36+
async function loadFonts(fonts) {
37+
return Promise.all(
38+
fonts.map(async (font) => ({
39+
...font,
40+
data: await (await fetch(font.url)).arrayBuffer(),
41+
}))
42+
);
43+
}
44+
45+
async function refreshGraphics() {
46+
const content = await renderToHTML(ChristmasCard, form.value);
47+
const markup = html(content);
48+
svg.value = await satori(markup, { width: 1080, height: 1080, fonts: fonts.value });
49+
}
50+
51+
async function handleFileChange(event: Event) {
52+
const file = (event.target as HTMLInputElement)?.files?.[0];
53+
if (file && file.size > 100 * 1024) throw new Error("File size must be below 100kb");
54+
const reader = new FileReader();
55+
reader.onload = () => (form.value.photo = reader.result as string);
56+
reader.readAsDataURL(file);
57+
}
58+
59+
60+
async function renderToHTML(Component, props = {}) {
61+
return await renderToString(createSSRApp(Component, props));
62+
63+
}
64+
65+
function downloadSvgAsJpeg(svgString, filename = "image.jpeg") {
66+
const blob = new Blob([svgString], { type: "image/svg+xml" });
67+
const url = URL.createObjectURL(blob);
68+
const img = new Image();
69+
70+
img.onload = () => {
71+
const canvas = document.createElement("canvas");
72+
canvas.width = canvas.height = 1080;
73+
canvas.getContext("2d")?.drawImage(img, 0, 0, 1080, 1080);
74+
const link = document.createElement("a");
75+
link.href = canvas.toDataURL("image/jpeg");
76+
link.download = filename;
77+
link.click();
78+
URL.revokeObjectURL(url);
79+
};
80+
img.src = url;
81+
}
82+
</script>
83+
84+
<style>
85+
.banner-here svg {
86+
width: 100%;
87+
height: 100%;
88+
}
89+
</style>

components/ChristmasCard.vue

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<template>
2+
<div class="w-full bg-red-500 h-full flex flex-col text-white text-7xl font-bold p-10 items-center justify-center"
3+
style="background-image: url(/img/sincerely-media-8EhZobADF8M-unsplash.jpg);">
4+
<div class="font-bold mb-8 " style="font-size: 100px;"> {{ greeting }}</div>
5+
<img v-if="photo" :src="photo" class="rounded-full border-4 border-red-900 mb-8" style="width:400px;height:400px">
6+
<div class="bg-red-500 px-2 py-2">From</div>
7+
<div> {{ name || 'Add your name here' }}</div>
8+
</div>
9+
</template>
10+
11+
<script setup lang="ts">
12+
defineProps(['name', 'greeting', 'photo'])
13+
</script>

nuxt.config.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default defineNuxtConfig({
2+
ssr: false,
3+
devtools: { enabled: true },
4+
modules: ["@nuxtjs/tailwindcss"],
5+
});

package.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "build-a-greeting-card-maker-with-vue-3-and-satori",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"build": "nuxt build",
7+
"dev": "nuxt dev",
8+
"generate": "nuxt generate",
9+
"preview": "nuxt preview",
10+
"postinstall": "nuxt prepare"
11+
},
12+
"dependencies": {
13+
"@vue/server-renderer": "^3.5.13",
14+
"@vueuse/core": "^12.0.0",
15+
"nuxt": "^3.14.1592",
16+
"satori": "^0.12.0",
17+
"satori-html": "^0.3.2"
18+
},
19+
"devDependencies": {
20+
"@nuxtjs/tailwindcss": "^6.12.2"
21+
}
22+
}

public/favicon.ico

4.19 KB
Binary file not shown.
66.4 KB
Binary file not shown.
Loading

tsconfig.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
// https://nuxt.com/docs/guide/concepts/typescript
3+
"extends": "./.nuxt/tsconfig.json"
4+
}

0 commit comments

Comments
 (0)