mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-10 21:07:46 +00:00
Fix merge conflicts
This commit is contained in:
10
src/routes/docs/tutorials/nuxt/+layout.svelte
Normal file
10
src/routes/docs/tutorials/nuxt/+layout.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
11
src/routes/docs/tutorials/nuxt/+layout.ts
Normal file
11
src/routes/docs/tutorials/nuxt/+layout.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load: LayoutLoad = ({ url }) => {
|
||||
const tutorials = import.meta.glob('./**/*.markdoc', {
|
||||
eager: true
|
||||
});
|
||||
return {
|
||||
tutorials,
|
||||
pathname: url.pathname
|
||||
};
|
||||
};
|
||||
6
src/routes/docs/tutorials/nuxt/+page.ts
Normal file
6
src/routes/docs/tutorials/nuxt/+page.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
throw redirect(303, '/docs/tutorials/nuxt/step-1');
|
||||
};
|
||||
@@ -6,6 +6,7 @@ step: 1
|
||||
difficulty: beginner
|
||||
readtime: 25
|
||||
framework: Nuxt
|
||||
category: Web
|
||||
back: /docs
|
||||
---
|
||||
|
||||
|
||||
161
src/routes/docs/tutorials/nuxt/step-2/+page.markdoc
Normal file
161
src/routes/docs/tutorials/nuxt/step-2/+page.markdoc
Normal file
@@ -0,0 +1,161 @@
|
||||
---
|
||||
layout: tutorial
|
||||
title: Create app
|
||||
description: Create a Nuxt app project and integrate with Appwrite
|
||||
step: 2
|
||||
---
|
||||
|
||||
# Create Nuxt project {% #create-nuxt-project %}
|
||||
|
||||
Create a Nuxt app with the `npx init` command.
|
||||
The command will install all the necessary dependencies for you.
|
||||
|
||||
```sh
|
||||
npx nuxi@latest init ideas-tracker
|
||||
```
|
||||
|
||||
# Add dependencies {% #add-dependencies %}
|
||||
|
||||
Once the project is created, change your current working directory and install the JavaScript Appwrite SDK.
|
||||
|
||||
```sh
|
||||
cd ideas-tracker
|
||||
npm install appwrite
|
||||
npm install "@appwrite.io/pink"
|
||||
```
|
||||
|
||||
Open `App.vue` and import the relevant style files.
|
||||
|
||||
```html
|
||||
<!-- app.vue -->
|
||||
|
||||
<script setup>
|
||||
|
||||
import "@appwrite.io/pink";
|
||||
// optionally, add icons
|
||||
import "@appwrite.io/pink-icons";
|
||||
|
||||
</script>
|
||||
```
|
||||
|
||||
Then update `nuxt.config.ts` to disable SSR for now. SSR support is coming soon to Appwrite, for now, disable SSR.
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
devtools: { enabled: true }
|
||||
})
|
||||
```
|
||||
|
||||
You can start the development server to watch your app update in the browser as you make your changes.
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
# File structure {% #file-structure %}
|
||||
|
||||
Nuxt relies on an opiniated directory structure to automate tasks and help organize the codebase.
|
||||
To take advantage of this we need to add the following directories:
|
||||
- `/components/` to keep our UI components in one place.
|
||||
We will get back to it in [step 5](/docs/tutorials/nuxt/step-5)
|
||||
- `/composables/`for storing files handling global states and data fetching.
|
||||
We will use it in [step 4](/docs/tutorials/nuxt/step-4)
|
||||
- `/layouts/` to store the page layouts
|
||||
- `/pages/` for the content pages.
|
||||
|
||||
Run the following command to create these folders
|
||||
|
||||
```sh
|
||||
mkdir components composables layouts pages
|
||||
```
|
||||
|
||||
# Add layout {% #add-layout %}
|
||||
|
||||
Go to the `layouts/` directory and add the file `default.vue`.
|
||||
Add the following code for the default layout.
|
||||
As you see it's nearly empty but it is needed for the automatic routing to work properly.
|
||||
|
||||
```html
|
||||
<!-- layouts/default.vue -->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
layout: "default",
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
# Add home page {% #add-home-page %}
|
||||
|
||||
Next, head over to the `pages`directory.
|
||||
This is where we will keep the content that will render on our pages in the web application.
|
||||
Each file you put in here will automatically become a route.
|
||||
Add the file `index.vue` with the following code.
|
||||
|
||||
```vue
|
||||
<!-- pages/index.vue -->
|
||||
|
||||
<template>
|
||||
<nav class="main-header u-padding-inline-end-0">
|
||||
<h3 class="u-stretch eyebrow-heading-1">Hello, Idea Tracker!</h3>
|
||||
</nav>
|
||||
</template>
|
||||
```
|
||||
|
||||
This is what your directory should look like after adding the new directories and files:
|
||||
|
||||
```
|
||||
[repository tree]
|
||||
├── .nuxt/
|
||||
├── components/
|
||||
├── composables/
|
||||
├── layouts/
|
||||
│ └── default.vue
|
||||
├── pages/
|
||||
│ ├── index.vue
|
||||
├── public/
|
||||
│ └── /favicon.ico
|
||||
├── server/
|
||||
│ └── /tsconfig.json
|
||||
├── .gitignore
|
||||
├── app.vue
|
||||
├── nuxt.config.ts
|
||||
├── package-lock.json
|
||||
├── package.json
|
||||
├── README.md
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
# Render page {% #render-page %}
|
||||
|
||||
If you run the development server now, it will still render the Nuxt Welcome page.
|
||||
We need to tell our app to use the files we just created instead.
|
||||
Open `app.vue` in the root directory and replace the content with the following code.
|
||||
Your page will now be up and running.
|
||||
|
||||
```vue
|
||||
<!-- app.vue -->
|
||||
|
||||
<script setup>
|
||||
import "@appwrite.io/pink";
|
||||
import "@appwrite.io/pink-icons";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
73
src/routes/docs/tutorials/nuxt/step-3/+page.markdoc
Normal file
73
src/routes/docs/tutorials/nuxt/step-3/+page.markdoc
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
layout: tutorial
|
||||
title: Set up Appwrite
|
||||
description: Import and configure a project with Appwrite Cloud.
|
||||
step: 3
|
||||
---
|
||||
|
||||
# Create project {% #create-project %}
|
||||
|
||||
Head to the [Appwrite Console](https://cloud.appwrite.io/console).
|
||||
|
||||
{% only_dark %}
|
||||

|
||||
{% /only_dark %}
|
||||
{% only_light %}
|
||||

|
||||
{% /only_light %}
|
||||
|
||||
If this is your first time using Appwrite, create an account and create your first project.
|
||||
|
||||
Then, under **Add a platform**, add a **Web app**.
|
||||
The **Hostname** should be `localhost`.
|
||||
|
||||
{% only_dark %}
|
||||

|
||||
{% /only_dark %}
|
||||
{% only_light %}
|
||||

|
||||
{% /only_light %}
|
||||
|
||||
You can skip the optional steps.
|
||||
|
||||
# Environment variables {% #environment-variables %}
|
||||
|
||||
To connect to Appwrite in our app, we'll need to use sensitive information.
|
||||
We keep the secrets by using environment variables for the endpoint and project id.
|
||||
Your project id is located in the **Settings** page in the Appwrite console.
|
||||
|
||||
{% only_dark %}
|
||||

|
||||
{% /only_dark %}
|
||||
{% only_light %}
|
||||

|
||||
{% /only_light %}
|
||||
|
||||
Add a `.env` file to the root directory and add the following code to it, replacing `YOUR_PROJECT_ID` with your project id.
|
||||
|
||||
```
|
||||
VITE_APPWRITE_ENDPOINT="https://cloud.appwrite.io/v1"
|
||||
VITE_APPWRITE_PROJECT="YOUR_PROJECT_ID"
|
||||
```
|
||||
|
||||
# Initialize Appwrite SDK {% #init-sdk %}
|
||||
|
||||
Create a new file `appwrite.js` for the Appwrite related code.
|
||||
Only one instance of the `Client()` class should be created per app.
|
||||
Add the following code to it.
|
||||
|
||||
```ts
|
||||
// appwrite.ts
|
||||
|
||||
import { Client, Databases, Account } from "appwrite";
|
||||
|
||||
const url: string = import.meta.env.VITE_APPWRITE_ENDPOINT;
|
||||
const project: string = import.meta.env.VITE_APPWRITE_PROJECT;
|
||||
|
||||
const client: Client = new Client();
|
||||
|
||||
client.setEndpoint(url).setProject(project);
|
||||
|
||||
export const account: Account = new Account(client);
|
||||
export const database: Databases = new Databases(client);
|
||||
```
|
||||
199
src/routes/docs/tutorials/nuxt/step-4/+page.markdoc
Normal file
199
src/routes/docs/tutorials/nuxt/step-4/+page.markdoc
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
layout: tutorial
|
||||
title: Add authentication
|
||||
description: Add authentication to your Nuxt application using Appwrite Web SDK
|
||||
step: 4
|
||||
---
|
||||
|
||||
For our ideas tracker app, we want any visitor to be able to read the ideas that are stored.
|
||||
On the other hand, we don't want the page spammed with just about anything from anyone just stopping by.
|
||||
To prevent that, or at least making it a bit more difficult, editing ideas will be available for logged in users only.
|
||||
With a login function, we can differentiate between users and decide which users have access to which content.
|
||||
|
||||
We will build a page with a simple login form and store its related logic in a `useUserSession` composable so it can be reused, starting with the composable.
|
||||
|
||||
# User session composable {% #user-session-composable %}
|
||||
|
||||
There are a few standard functions involved in handling a user session that is added to the composable.
|
||||
The user needs to be able to register to an account, login to the account and logout from it.
|
||||
|
||||
We are using Appwrite as a backend to handle the user details, so we need to connect to Appwrite by importing the configurations from step 3.
|
||||
The response from these interactions will be stored as references to get more information about the user in our app.
|
||||
|
||||
In your `composable` directory, create the file `useUserSession.js` and add the following code.
|
||||
Then you can call the `useUserSession()` function in the pages and components to use the functionality.
|
||||
|
||||
```ts
|
||||
// composable/useUserSession.ts
|
||||
|
||||
import { ID } from "appwrite";
|
||||
import { ref } from "vue";
|
||||
import { account } from "../appwrite";
|
||||
import { type Models } from 'appwrite';
|
||||
|
||||
const current = ref<Models.Session | null>(null); // Reference to current user object
|
||||
|
||||
export const useUserSession = () => {
|
||||
const register = async (email: string, password: string): Promise<void> => {
|
||||
await account.create(ID.unique(), email, password); // Register new user in Appwrite
|
||||
await login(email, password); // Login registered user
|
||||
};
|
||||
|
||||
const login = async (email: string, password: string): Promise<void> => {
|
||||
const authUser = await account.createEmailSession(email, password); // Open user session in Appwrite
|
||||
current.value = authUser; // Pass user data to current ref
|
||||
navigateTo("/");
|
||||
};
|
||||
|
||||
const logout = async (): Promise<void> => {
|
||||
await account.deleteSession("current"); // Delete Appwrite user session
|
||||
current.value = null; // Clear current ref
|
||||
navigateTo("/");
|
||||
};
|
||||
|
||||
// Check if already logged in to initialize the store.
|
||||
account.getSession('current').then((user: Models.Session) => {
|
||||
current.value = user;
|
||||
});
|
||||
|
||||
return {
|
||||
current,
|
||||
login,
|
||||
logout,
|
||||
register,
|
||||
};
|
||||
};
|
||||
```
|
||||
# Login page {% #login-page %}
|
||||
|
||||
Create a new file in the `pages` directory called `login.vue`.
|
||||
This will not only create a new page, it will also add the route `/login` to the url.
|
||||
In step 5 we will add a login button that will redirect us to this page.
|
||||
|
||||
We will define functions to handle form submissions and show either a signup
|
||||
or a login form, which renders one form or the other depending on `isSignUp`'s state.
|
||||
We will also show buttons to toggle between the two different types of forms.
|
||||
|
||||
```vue
|
||||
<!-- pages/login.vue -->
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// Access user composable functions
|
||||
const user = useUserSession();
|
||||
|
||||
const isSignUp = ref(false);
|
||||
|
||||
// Login user event handler
|
||||
const handleLogin = async (event) => {
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
|
||||
await user.login(formData.get('email'), formData.get('password'));
|
||||
|
||||
form.reset(); // Clear the form
|
||||
};
|
||||
|
||||
const handleRegistration = async (event) => {
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
|
||||
await user.register(formData.get('email'), formData.get('password'));
|
||||
|
||||
form.reset(); // Clear the form
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="u-max-width-650" style="margin: 0 auto;">
|
||||
<section class="card u-margin-32">
|
||||
<h2 class="eyebrow-heading-2">Login/Register</h2>
|
||||
<AuthForm v-if="isSignUp" :handle-submit="handleRegistration" submit-type="Sign Up"></AuthForm>
|
||||
<AuthForm v-else :handle-submit="handleLogin" submit-type="Log In"></AuthForm>
|
||||
<button v-if="isSignUp" @click="isSignUp = false" class="u-margin-block-start-16">
|
||||
Already have an account? Log in
|
||||
</button>
|
||||
<button v-else @click="isSignUp = true" class="u-margin-block-start-16">
|
||||
Don't have an account? Sign up
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
# Authentication forms {% #auth-forms %}
|
||||
|
||||
In the previous step, we defined a `AuthForm` to handle signup and login.
|
||||
Let's build this form now.
|
||||
|
||||
Create a new file `components/authForm.vue` and add the following code.
|
||||
The handle submit and submit type are props passed from `pages/login.vue`
|
||||
|
||||
```html
|
||||
<!-- components/authForm.vue -->
|
||||
|
||||
<template>
|
||||
<form
|
||||
class="form u-width-full-line u-max-width-500 u-margin-block-start-16"
|
||||
@submit.prevent="handleSubmit"
|
||||
>
|
||||
<ul class="form-list">
|
||||
<!-- Input field e-mail -->
|
||||
<li class="form-item">
|
||||
<label class="label">Email</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
class="input-text"
|
||||
placeholder="Email"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<!-- Input field e-mail -->
|
||||
<li class="form-item">
|
||||
<label class="label">Password</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
class="input-text"
|
||||
placeholder="Password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="buttons-list u-margin-block-start-16">
|
||||
<!-- Login button -->
|
||||
<li class="buttons-list-item">
|
||||
<button
|
||||
class="button is-small u-margin-inline-start-4"
|
||||
aria-label="Login"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{submitType}}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
handleSubmit: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
submitType: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
Go to the browser and add `/login` to the url to check out the new page.
|
||||
68
src/routes/docs/tutorials/nuxt/step-5/+page.markdoc
Normal file
68
src/routes/docs/tutorials/nuxt/step-5/+page.markdoc
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
layout: tutorial
|
||||
title: Add navigation
|
||||
description: Add navigation to your app.
|
||||
step: 5
|
||||
---
|
||||
|
||||
To help our users navigate the app we want it to have a navigation bar that's visible on all pages.
|
||||
We will once again use the `useUserSession()` composable for information about the current user.
|
||||
|
||||
With this piece of information we will show a login button when no user is logged in and a logout button when one is.
|
||||
We will also put the user's e-mail address next to the logout button.
|
||||
|
||||
From the `components` directory, create the file `navbar.vue` and add the code below.
|
||||
|
||||
```vue
|
||||
<!-- components/navbar.vue -->
|
||||
<script setup>
|
||||
// Access user composable function
|
||||
const user = useUserSession();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!--- Navbar -->
|
||||
<nav class="main-header u-padding-inline-end-0">
|
||||
<h3 class="u-stretch eyebrow-heading-1">Idea Tracker</h3>
|
||||
<!-- Email and logout button if logged in user -->
|
||||
<div
|
||||
class="main-header-end u-margin-inline-end-16"
|
||||
v-if="user.current.value"
|
||||
>
|
||||
<p>{{ user.current.providerUid }}</p>
|
||||
<button class="button" type="button" @click="user.logout()">
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
<!-- Login button if no user logged in -->
|
||||
<NuxtLink v-else href="/login" class="button u-margin-inline-end-16">
|
||||
Login
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Open `app.vue` from the root directory and add the navigation bar.
|
||||
|
||||
```vue
|
||||
<!-- app.vue -->
|
||||
|
||||
<script setup>
|
||||
import "@appwrite.io/pink";
|
||||
import "@appwrite.io/pink-icons";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLayout>
|
||||
<!-- Add navbar -->
|
||||
<Navbar />
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Have a look in the browser at both the main page and the login page to test the new functionality.
|
||||
115
src/routes/docs/tutorials/nuxt/step-6/+page.markdoc
Normal file
115
src/routes/docs/tutorials/nuxt/step-6/+page.markdoc
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
layout: tutorial
|
||||
title: Add database
|
||||
description: Add databases and queries for ideas in your Nuxt project.
|
||||
step: 6
|
||||
---
|
||||
|
||||
In Appwrite, data is stored as a collection of documents.
|
||||
Create a new database and collection in the [Appwrite Console](https://cloud.appwrite.io/) to store the ideas.
|
||||
|
||||
{% only_dark %}
|
||||

|
||||
{% /only_dark %}
|
||||
{% only_light %}
|
||||

|
||||
{% /only_light %}
|
||||
|
||||
Create a new collection with the following attributes:
|
||||
| Field | Type | Required | Size |
|
||||
|-------------|--------|----------|----------|
|
||||
| userId | String | Yes | 200 |
|
||||
| title | String | Yes | 200 |
|
||||
| description | String | No | 500 |
|
||||
|
||||
Change the collection's permissions in the settings to give access.
|
||||
|
||||
{% only_dark %}
|
||||

|
||||
{% /only_dark %}
|
||||
{% only_light %}
|
||||

|
||||
{% /only_light %}
|
||||
|
||||
Navigate to the **Settings** tab of your collection, add the role **Any** and check the **Read** box.
|
||||
Next, add a **Users** role and give them access to **Create**, **Update** and **Delete** by checking those boxes.
|
||||
|
||||
# Environment variables {% #environment-variables %}
|
||||
|
||||
Just like when we set up the connection to Appwrite in [step 3](/docs/tutorials/nuxt/step-3), we need to keep the variables with the collection id secret.
|
||||
Open the `.env` file and add your database id and your collection id to it.
|
||||
|
||||
```
|
||||
VITE_DATABASE_ID="YOUR_DATABASE_ID"
|
||||
VITE_COLLECTION_ID="YOUR_COLLECTION_ID"
|
||||
```
|
||||
|
||||
# Query methods {% #query-methods %}
|
||||
|
||||
Now that we have a collection in the database to hold ideas, we can connect to it from our app.
|
||||
Our users should be able to read, add and remove ideas.
|
||||
We will add a new composable, `useIdeas`, to handle this functionality.
|
||||
|
||||
Create a new file in the composables directory, `useIdeas.js` and add the following code.
|
||||
|
||||
```ts
|
||||
// composables/useIdeas.ts
|
||||
|
||||
import { ID, Query, Models} from "appwrite";
|
||||
import { database } from "~/appwrite";
|
||||
import { ref } from "vue";
|
||||
|
||||
const ideasDatabaseId: string = import.meta.env.VITE_DATABASE_ID;
|
||||
const ideasCollectionId: string = import.meta.env.VITE_COLLECTION_ID;
|
||||
const queryLimit: number = 10;
|
||||
|
||||
interface Idea extends Models.Document{
|
||||
title: string;
|
||||
description: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const current = ref<Idea[] | null>(null); // Reference for the fetched data
|
||||
|
||||
export const useIdeas = () => {
|
||||
|
||||
// Fetch the 10 most recent ideas from the database
|
||||
// Add the list to the current reference object
|
||||
const fetch = async (): Promise<void> => {
|
||||
const response = await database.listDocuments(
|
||||
ideasDatabaseId,
|
||||
ideasCollectionId,
|
||||
[Query.orderDesc("$createdAt"), Query.limit(queryLimit)]
|
||||
);
|
||||
current.value = response.documents as Idea[];
|
||||
};
|
||||
|
||||
// Add new idea to the database,
|
||||
// Change the value of the current object
|
||||
const add = async (idea: Idea): Promise<void> => {
|
||||
const response = await database.createDocument(
|
||||
ideasDatabaseId,
|
||||
ideasCollectionId,
|
||||
ID.unique(),
|
||||
idea
|
||||
);
|
||||
current.value = [response, ...current.value as Idea[]].slice(0, 10) as Idea[];
|
||||
};
|
||||
|
||||
const remove = async (id: string): Promise<void> => {
|
||||
await database.deleteDocument(ideasDatabaseId, ideasCollectionId, id);
|
||||
await fetch(); // Refetch ideas to ensure we have 10 items
|
||||
};
|
||||
|
||||
fetch();
|
||||
|
||||
return {
|
||||
add,
|
||||
current,
|
||||
fetch,
|
||||
remove,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Now we can call the `useIdeas` composable from the home page.
|
||||
181
src/routes/docs/tutorials/nuxt/step-7/+page.markdoc
Normal file
181
src/routes/docs/tutorials/nuxt/step-7/+page.markdoc
Normal file
@@ -0,0 +1,181 @@
|
||||
---
|
||||
layout: tutorial
|
||||
title: Ideas page
|
||||
description: Add ideas from Appwrite database in your app.
|
||||
step: 7
|
||||
---
|
||||
|
||||
With the methods in the `useIdeas` composable we can get some ideas to the home page for the users to interact with.
|
||||
We will use it in a form component so the logged in users can add their ideas, and in a list component to render the ten most recent ideas.
|
||||
We start with building the form.
|
||||
|
||||
# Idea form {% #idea-form %}
|
||||
|
||||
On the home page, the logged in users should be able to add their ideas to the Appwrite database.
|
||||
The form need a text field for filling in the title, a textarea for the description and a submit button.
|
||||
|
||||
From the `components` directory, add the file `IdeasForm.vue` and add the following code.
|
||||
|
||||
```vue
|
||||
<!-- components/IdeasForm.vue -->
|
||||
<script setup>
|
||||
const ideas = useIdeas();
|
||||
const user = useUserSession();
|
||||
|
||||
const handleAddIdea = async (event) => {
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
|
||||
// Extract the values from the FormData object and add userId
|
||||
const postIdeaData = {
|
||||
userId: user.current.value.userId,
|
||||
title: formData.get('title'),
|
||||
description: formData.get('description'),
|
||||
};
|
||||
|
||||
await ideas.add(postIdeaData);
|
||||
|
||||
form.reset(); // Clear the form
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<article class="container padding-0">
|
||||
<h4 class="heading-level-4">Submit Idea</h4>
|
||||
<form @submit.prevent="handleAddIdea" class="u-margin-block-start-16">
|
||||
<ul class="form-list">
|
||||
<li class="form-item">
|
||||
<label class="label">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Title"
|
||||
name="title"
|
||||
/>
|
||||
</li>
|
||||
<li class="form-item">
|
||||
<label class="label">Description</label>
|
||||
<textarea
|
||||
placeholder="Description"
|
||||
name="description"
|
||||
/>
|
||||
</li>
|
||||
<button class="button" aria-label="Submit idea" type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</ul>
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Next, add the component to the page `pages/index.vue` by auto-importing it in the `<template>` tag.
|
||||
In doing that, we need to take a moment to think about how we want to display the form to the users.
|
||||
|
||||
Since it should only be shown to logged in user, we need to wrap it in a `<section>` that renders conditionally when the `isLoggedIn` reference in the `useUserSession` is true.
|
||||
If the requirement is not met, we show a paragraph with some information to the user instead.
|
||||
|
||||
Add the following code to the `index.vue` page to conditionally render the form and information paragraph.
|
||||
|
||||
Overwrite the contents of `pages/index.vue` with the following code.
|
||||
|
||||
```vue
|
||||
<!-- pages/index.vue -->
|
||||
<script setup>
|
||||
const user = useUserSession();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="u-max-width-650" style="margin-inline: auto;">
|
||||
<!-- Idea form component for logged in users -->
|
||||
<section v-if="user.current.value" class="card u-margin-32">
|
||||
<IdeasForm />
|
||||
</section>
|
||||
|
||||
<section v-else class="card u-margin-32">
|
||||
<div class="container">
|
||||
<p class="body-text-1" style="width: 100%;">
|
||||
Please login to submit an idea.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<IdeasList />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
article.box {
|
||||
background-color: hsl(var(--color-neutral-0));
|
||||
}
|
||||
</style>
|
||||
````
|
||||
|
||||
# Ideas list {% #ideas-list %}
|
||||
Now that we can get some ideas to show, we go on to build the component for the list of ideas.
|
||||
|
||||
Once again, we need to take a moment to think about how this component should work.
|
||||
First of all, the ideas should be visible for the users before any interaction has taken place on the page.
|
||||
To catch that moment in time when the page loads, we call our `fetch` function, that fetches the ideas in Appwrite, from the built-in `onMounted` function.
|
||||
|
||||
Second, it's likely that a user will want to delete one of their ideas from the database.
|
||||
We help them do that by adding a delete button in the top right corner of the idea list item, but only on the ideas added by the user itself.
|
||||
|
||||
Add the file `IdeasList` from the `componenents` directory and insert the following code:
|
||||
|
||||
```vue
|
||||
<!-- componenents/IdeasList.vue -->
|
||||
<script setup>
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
const ideas = useIdeas();
|
||||
const user = useUserSession();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="u-margin-32">
|
||||
<article class="card">
|
||||
<h4 class="heading-level-4">Latest Ideas</h4>
|
||||
<ul class="u-margin-block-start-8">
|
||||
<template v-if="ideas.current.value && ideas.current.value.length">
|
||||
<li v-for="idea in ideas.current.value">
|
||||
<div class="box">
|
||||
<h5 class="heading-level-6">{{ idea.title }}</h5>
|
||||
<p class="body-text-2">{{ idea.description }}</p>
|
||||
<div class="u-position-absolute u-inset-inline-end-8 u-inset-block-start-8">
|
||||
<button class="button is-small is-text is-only-icon" aria-label="Remove item" v-if="user.current.value &&
|
||||
idea.userId === user.current.value.userId
|
||||
" type="button" @click="ideas.remove(idea.$id)">
|
||||
<span class="icon-document-remove" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>No ideas yet.</p>
|
||||
</template>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
Return to the file `pages/index.vue` once more to import the list of ideas.
|
||||
This component should be visible to all users, so no conditional rendering neeeds to be handled.
|
||||
|
||||
```vue
|
||||
<!-- pages/index.vue -->
|
||||
|
||||
<template>
|
||||
<div class="u-max-width-650" style="margin: 0 auto;">
|
||||
... Some skipped code
|
||||
<IdeasList />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
.. Skipped script section and styling
|
||||
````
|
||||
|
||||
Congratulations!
|
||||
You now have an ideas tracker built with Nuxt and Appwrite to use locally.
|
||||
11
src/routes/docs/tutorials/nuxt/step-8/+page.markdoc
Normal file
11
src/routes/docs/tutorials/nuxt/step-8/+page.markdoc
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: tutorial
|
||||
title: Next steps
|
||||
description: View your Nuxt app built on Appwrite Cloud.
|
||||
step: 8
|
||||
---
|
||||
|
||||
# Test your project {% #test-project %}
|
||||
Run your project with `npm run dev` and open the URL shown by the NPM command in your browser.
|
||||
|
||||
Head to the [Appwrite Console](https://cloud.appwrite.io/console) to see the new users and follow their interactions.
|
||||
Reference in New Issue
Block a user