Compare commits

...

9 Commits

11 changed files with 470 additions and 269 deletions

3
.gitignore vendored
View File

@@ -22,3 +22,6 @@ logs
.env
.env.*
!.env.example
# Local DB
.db

View File

@@ -59,6 +59,12 @@ button:active {
transform: translateY(1px);
}
#__nuxt {
display: flex;
width: 100%;
height: 100%;
}
#app {
display: flex;
width: 100%;

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const props = defineProps<{
contact: Pick<ContactRecord, "favorite">
}>();
const favorite = computed(() => props.contact.favorite);
const ariaLabel = computed(() => favorite.value ? "Remove from favorites" : "Add to favorites");
</script>
<template>
<form method="post">
<button
:aria-label=ariaLabel
name="favorite"
:value="() => favorite ? 'false': 'true'"
>
{{ favorite ? "★" : "☆" }}
</button>
</form>
</template>
<style scoped>
</style>

20
error.vue Normal file
View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
import type { NuxtError } from '#app';
const props = defineProps<{
error: NuxtError
}>();
// const handleError = () => clearError({ redirect: '/' });
</script>
<template>
<main id="error-page">
<h1 v-if="props.error.statusCode === 404">404</h1>
<h1 v-else>Error!</h1>
<p>{{ props.error.message }}</p>
<pre v-if="props.error.stack">
<code>{{ props.error.stack }}</code>
</pre>
</main>
</template>

55
layouts/default.vue Normal file
View File

@@ -0,0 +1,55 @@
<script setup lang="ts">
const { $trpc } = useNuxtApp();
const { data: contacts } = await $trpc.contactList.useQuery();
</script>
<template>
<div id="app">
<div id="sidebar">
<h1>
<NuxtLink to="/about">Nuxt Contacts</NuxtLink>
</h1>
<div>
<form id="search-form" role="search">
<input
id="q"
aria-label="Search contacts"
name="q"
placeholder="Search"
type="search"
>
<div
id="search-spinner"
aria-hidden
hidden
/>
</form>
<form method="post">
<button type="submit">New</button>
</form>
</div>
<nav>
<ul v-if="contacts?.length">
<li v-for="contact in contacts" :key="contact.id">
<NuxtLink :to="{name: 'contacts-contactId', params: {contactId: contact.id}}">
<template v-if="contact.first || contact.last">{{ contact.first }} {{ contact.last }}</template>
<i v-else>No Name</i>
<span v-if="contact.favorite"></span>
</NuxtLink>
</li>
<li>
<NuxtLink :to="{name: 'contacts-contactId', params: {contactId: 1}}">Your Name</NuxtLink>
</li>
<li>
<NuxtLink :to="{name: 'contacts-contactId', params: {contactId: 2}}">Your Friend</NuxtLink>
</li>
</ul>
<p v-else><i>No contacts</i></p>
</nav>
</div>
<div id="detail">
<slot/>
</div>
</div>
</template>

View File

@@ -7,4 +7,7 @@ export default defineNuxtConfig({
build: {
transpile: ['trpc-nuxt']
},
routeRules: {
'/about': { prerender: true },
}
});

View File

@@ -28,5 +28,11 @@
"devDependencies": {
"@types/node": "^22.14.0",
"typescript": "^5.8.2"
},
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"esbuild"
]
}
}

51
pages/about.vue Normal file
View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
definePageMeta({
layout: false,
});
</script>
<template>
<div id="about">
<NuxtLink to="/"> Go to demo</NuxtLink>
<h1>About React Router Contacts</h1>
<div>
<p>
This is a demo application showing off some of the
powerful features of React Router, including
dynamic routing, nested routes, loaders, actions,
and more.
</p>
<h2>Features</h2>
<p>
Explore the demo to see how React Router handles:
</p>
<ul>
<li>
Data loading and mutations with loaders and
actions
</li>
<li>
Nested routing with parent/child relationships
</li>
<li>URL-based routing with dynamic segments</li>
<li>Pending and optimistic UI</li>
</ul>
<h2>Learn More</h2>
<p>
Check out the official documentation at{{ " " }}
<a href="https://reactrouter.com">
reactrouter.com
</a>{{ " " }}
to learn more about building great web
applications with React Router.
</p>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,58 @@
<script setup lang="ts">
const { $trpc } = useNuxtApp();
const route = useRoute();
const { data: contact } = await $trpc.contactGet.useQuery({ contactId: `${route.params.contactId}` });
if (contact.value === null) throw createError({ statusCode: 404, statusMessage: 'Not Found' });
function deleteSubmit(event: Event) {
const response = window.confirm(
'Please confirm you want to delete this record.'
);
if (!response) {
event.preventDefault();
}
}
</script>
<template>
<div id="contact">
<div>
<img
:key="contact.avatar"
:alt="() => `${contact.first} ${contact.last} avatar`"
:src="contact.avatar"
>
</div>
<div>
<h1>
<template v-if="contact.first || contact.last">
{{ `${contact.first} ${contact.last}` }}
</template>
<i v-else>No Name</i>
<ContactFavorite :contact="contact"/>
</h1>
<p v-if="contact.twitter">
<a :href="`https://twitter.com/${contact.twitter}`">{{ contact.twitter }}</a>
</p>
<p v-if="contact.notes">{{ contact.notes }}</p>
<div>
<form action="edit">
<button type="submit">Edit</button>
</form>
<form
action="destroy"
method="post"
@submit="deleteSubmit">
<button type="submit">Delete</button>
</Form>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,50 +1,16 @@
<script setup lang="ts">
const { $trpc } = useNuxtApp();
const { data: hello } = await $trpc.hello.useQuery({ text: 'client' });
// Il typing sul risultato e' corretto, ma sul body no
const { data: otherHello } = await useFetch('/api/test/HELLO', {
method: 'post',
body: {
world: 'MONDO'
}
});
const clientSide = ref<{
trpcData: string,
fetchData: string,
} | null>(null);
async function loadNewData() {
const [{ data: a1 }, { data: b1 }] = await Promise.all([
$trpc.hello.useQuery({ text: 'client' }),
useFetch('/api/test/HELLO', {
method: 'post',
body: {
world: 'MONDO'
}
})
]);
clientSide.value = { trpcData: a1.value?.greeting ?? 'no', fetchData: b1.value?.risultato ?? 'no2' };
}
</script>
<template>
<div>
<h1>Index Page</h1>
<div>
<p>RPC Client output: {{ hello }}</p>
<p>Nuxt API output: {{ otherHello?.risultato }}</p>
</div>
<div>
<button @click="loadNewData">Carica ClientSide</button>
<div v-if="clientSide">
<p>{{ clientSide.trpcData }}</p>
<p>{{ clientSide.fetchData }}</p>
</div>
</div>
</div>
<p id="index-page">
This is a demo for React Router.
<br>
Check out{" "}
<a href="https://reactrouter.com">
the docs at reactrouter.com
</a>
.
</p>
</template>
<style scoped>

View File

@@ -1,5 +1,6 @@
import { publicProcedure, router } from '~/server/trpc/init';
import { z } from 'zod';
import { getContact, getContacts } from "~/server/utils/data";
export const appRouter = router({
hello: publicProcedure.input(
@@ -11,6 +12,14 @@ export const appRouter = router({
greeting: `hello ${opts.input.text}`,
};
}),
contactList: publicProcedure.query(async () => {
return await getContacts();
}),
contactGet: publicProcedure.input(
z.object({ contactId: z.string() })
).query(async (opts) => {
return await getContact(opts.input.contactId);
}),
});
// export type definition of API