Compare commits
9 Commits
b12db15bbe
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| e91dd6ac8d | |||
| 07c29d65b7 | |||
| 0d933f971f | |||
| e2165f2b25 | |||
| 30b6f08d84 | |||
| 9657f731ab | |||
| bfdceb73ec | |||
| e24b12ecd8 | |||
| 1c2fdc1a66 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,3 +22,6 @@ logs
|
|||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
|
# Local DB
|
||||||
|
.db
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
24
components/ContactFavorite.vue
Normal file
24
components/ContactFavorite.vue
Normal 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
20
error.vue
Normal 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
55
layouts/default.vue
Normal 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>
|
||||||
@@ -7,4 +7,7 @@ export default defineNuxtConfig({
|
|||||||
build: {
|
build: {
|
||||||
transpile: ['trpc-nuxt']
|
transpile: ['trpc-nuxt']
|
||||||
},
|
},
|
||||||
|
routeRules: {
|
||||||
|
'/about': { prerender: true },
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,5 +28,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.2"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"@parcel/watcher",
|
||||||
|
"esbuild"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
pages/about.vue
Normal file
51
pages/about.vue
Normal 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>
|
||||||
58
pages/contacts/[contactId].vue
Normal file
58
pages/contacts/[contactId].vue
Normal 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>
|
||||||
@@ -1,50 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<p id="index-page">
|
||||||
<h1>Index Page</h1>
|
This is a demo for React Router.
|
||||||
<div>
|
<br>
|
||||||
<p>RPC Client output: {{ hello }}</p>
|
Check out{" "}
|
||||||
<p>Nuxt API output: {{ otherHello?.risultato }}</p>
|
<a href="https://reactrouter.com">
|
||||||
</div>
|
the docs at reactrouter.com
|
||||||
<div>
|
</a>
|
||||||
<button @click="loadNewData">Carica ClientSide</button>
|
.
|
||||||
<div v-if="clientSide">
|
</p>
|
||||||
<p>{{ clientSide.trpcData }}</p>
|
|
||||||
<p>{{ clientSide.fetchData }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { publicProcedure, router } from '~/server/trpc/init';
|
import { publicProcedure, router } from '~/server/trpc/init';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { getContact, getContacts } from "~/server/utils/data";
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
hello: publicProcedure.input(
|
hello: publicProcedure.input(
|
||||||
@@ -11,6 +12,14 @@ export const appRouter = router({
|
|||||||
greeting: `hello ${opts.input.text}`,
|
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
|
// export type definition of API
|
||||||
|
|||||||
Reference in New Issue
Block a user