mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-11 04:22:19 +00:00
Fix alll suggestions
This commit is contained in:
@@ -27,6 +27,8 @@ npm install "@appwrite.io/pink"
|
||||
Open `App.vue` and import the relevant style files.
|
||||
|
||||
```html
|
||||
<!-- app.vue -->
|
||||
|
||||
<script setup>
|
||||
|
||||
import "@appwrite.io/pink";
|
||||
@@ -39,6 +41,8 @@ import "@appwrite.io/pink-icons";
|
||||
Then update `nuxt.config.ts` to disable SSR for now. SSR support is coming soon to Appwrite, for now, disable SSR.
|
||||
|
||||
```js
|
||||
// nuxt.config.ts
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
|
||||
@@ -19,7 +19,7 @@ Head to the [Appwrite Console](https://cloud.appwrite.io/console).
|
||||
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.
|
||||
The **Hostname** should be `localhost`.
|
||||
|
||||
{% only_dark %}
|
||||

|
||||
@@ -56,18 +56,18 @@ 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.
|
||||
|
||||
```js
|
||||
// appwrite.js
|
||||
```ts
|
||||
// appwrite.ts
|
||||
|
||||
import { Client, Databases, Account } from "appwrite";
|
||||
|
||||
const url = import.meta.env.VITE_APPWRITE_ENDPOINT;
|
||||
const project = import.meta.env.VITE_APPWRITE_PROJECT;
|
||||
const url: string = import.meta.env.VITE_APPWRITE_ENDPOINT;
|
||||
const project: string = import.meta.env.VITE_APPWRITE_PROJECT;
|
||||
|
||||
const client = new Client();
|
||||
const client: Client = new Client();
|
||||
|
||||
client.setEndpoint(url).setProject(project);
|
||||
|
||||
export const account = new Account(client);
|
||||
export const database = new Databases(client);
|
||||
export const account: Account = new Account(client);
|
||||
export const database: Databases = new Databases(client);
|
||||
```
|
||||
|
||||
@@ -23,45 +23,43 @@ The response from these interactions will be stored as references to get more in
|
||||
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.
|
||||
|
||||
```js
|
||||
// composable/useUserSession.js
|
||||
```ts
|
||||
// composable/useUserSession.ts
|
||||
|
||||
import { ID } from "appwrite";
|
||||
import { ref } from "vue";
|
||||
import { account } from "/appwrite";
|
||||
import { account } from "../appwrite";
|
||||
import { type Models } from 'appwrite';
|
||||
|
||||
const current = ref(null) // Reference to current user object
|
||||
const isLoggedIn = ref(null) //Reference to check user status
|
||||
|
||||
export const useUserSession = () => {
|
||||
const register = async (email, password) => {
|
||||
await account.create(ID.unique(), email, password); // Register new user in Appwrite
|
||||
await login(email, password); // Login registered user
|
||||
};
|
||||
|
||||
const login = async (email, password) => {
|
||||
const authUser = await account.createEmailSession(email, password); // Open user session in Appwrite
|
||||
current.value = authUser; // Pass user data to current ref
|
||||
isLoggedIn.value = true; // Set ref to true
|
||||
navigateTo("/");
|
||||
};
|
||||
const current = ref<Models.Session | null>(null); // Reference to current user object
|
||||
|
||||
const logout = async () => {
|
||||
await account.deleteSession("current"); // Delete Appwrite user session
|
||||
current.value = null; // Clear current ref
|
||||
isLoggedIn.value = false; // Set ref to false
|
||||
navigateTo("/");
|
||||
};
|
||||
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
|
||||
};
|
||||
|
||||
return {
|
||||
current,
|
||||
isLoggedIn,
|
||||
login,
|
||||
logout,
|
||||
register,
|
||||
};
|
||||
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("/");
|
||||
};
|
||||
|
||||
return {
|
||||
current,
|
||||
login,
|
||||
logout,
|
||||
register,
|
||||
};
|
||||
};
|
||||
|
||||
```
|
||||
# Login page {% #login-page %}
|
||||
|
||||
@@ -75,7 +73,7 @@ In step 5 we will add a loginbutton that will redirect us to this page.
|
||||
Add the following code to build the form.
|
||||
|
||||
```vue
|
||||
// pages/login.vue
|
||||
<!-- pages/login.vue -->
|
||||
|
||||
<template>
|
||||
<div class="u-max-width-650" style="margin: 0 auto;">
|
||||
@@ -140,35 +138,27 @@ Add the following code to build the form.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
setup() {
|
||||
// Access user composable functions
|
||||
const user = useUserSession();
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// Store user input
|
||||
const userData = {
|
||||
email: "",
|
||||
password: "",
|
||||
};
|
||||
// Access user composable functions
|
||||
const user = useUserSession();
|
||||
|
||||
// Login user event handler
|
||||
const handleLogin = async () => {
|
||||
await user.login(userData.email, userData.password);
|
||||
};
|
||||
// Store user input
|
||||
const userData = ref({
|
||||
email: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
// Register user event handler
|
||||
const handleRegistration = async () => {
|
||||
await user.register(userData.email, userData.password);
|
||||
};
|
||||
// Login user event handler
|
||||
const handleLogin = async () => {
|
||||
await user.login(userData.value.email, userData.value.password);
|
||||
};
|
||||
|
||||
return {
|
||||
handleLogin,
|
||||
handleRegistration,
|
||||
userData,
|
||||
};
|
||||
},
|
||||
};
|
||||
// Register user event handler
|
||||
const handleRegistration = async () => {
|
||||
await user.register(userData.value.email, userData.value.password);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ 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
|
||||
<!-- components/navbar.vue -->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
@@ -24,9 +24,9 @@ From the `components` directory, create the file `navbar.vue` and add the code b
|
||||
<!-- Email and logout button if logged in user -->
|
||||
<div
|
||||
class="main-header-end u-margin-inline-end-16"
|
||||
v-if="user.isLoggedIn.value === true"
|
||||
>
|
||||
<p>{{ user.current.value.providerUid }}</p>
|
||||
v-if="user.current.value"
|
||||
>
|
||||
<p>{{ user.current.providerUid }}</p>
|
||||
<button class="button" type="button" @click="user.logout()">
|
||||
Logout
|
||||
</button>
|
||||
@@ -39,32 +39,31 @@ From the `components` directory, create the file `navbar.vue` and add the code b
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
setup() {
|
||||
const user = useUserSession(); // Access user composable function
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
},
|
||||
};
|
||||
<script setup>
|
||||
// Access user composable function
|
||||
const user = useUserSession();
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
Open `app.vue` from the root directory and add the navigation bar.
|
||||
|
||||
```vue
|
||||
// app.vue
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<!-- Add navbar -->
|
||||
<Navbar />
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
<template>
|
||||
<!-- 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.
|
||||
|
||||
@@ -5,23 +5,32 @@ 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 collection in the [Appwrite Console](https://cloud.appwrite.io/) to store the ideas.
|
||||
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 |
|
||||
|-------------|--------|----------|
|
||||
| userId | String | Yes |
|
||||
| title | String | Yes |
|
||||
| description | String | No |
|
||||
| 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.
|
||||
|
||||
@@ -44,55 +53,60 @@ 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.
|
||||
|
||||
```js
|
||||
// composables/useIdeas.js
|
||||
// composables/useIdeas.ts
|
||||
|
||||
import { ID, Query } from "appwrite";
|
||||
import { ID, Query, Models} from "appwrite";
|
||||
import { database } from "~/appwrite";
|
||||
import { ref } from "vue";
|
||||
|
||||
const ideasDatabaseId = import.meta.env.VITE_DATABASE_ID;
|
||||
const ideasCollectionId = import.meta.env.VITE_COLLECTION_ID;
|
||||
const ideasDatabaseId: string = import.meta.env.VITE_DATABASE_ID;
|
||||
const ideasCollectionId: string = import.meta.env.VITE_COLLECTION_ID;
|
||||
|
||||
const current = ref(null); //Reference for the fetched data
|
||||
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 () => {
|
||||
const response = await database.listDocuments(
|
||||
ideasDatabaseId,
|
||||
ideasCollectionId,
|
||||
[Query.orderDesc("$createdAt"), Query.limit(10)]
|
||||
);
|
||||
current.value = response.documents;
|
||||
};
|
||||
// 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(10)]
|
||||
);
|
||||
current.value = response.documents as Idea[];
|
||||
};
|
||||
|
||||
// Add new idea to the database,
|
||||
// Change the value of the current object
|
||||
const add = async (idea) => {
|
||||
const response = await database.createDocument(
|
||||
ideasDatabaseId,
|
||||
ideasCollectionId,
|
||||
ID.unique(),
|
||||
idea
|
||||
);
|
||||
current.value = [response, ...current.value].slice(0, 10);
|
||||
};
|
||||
// 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) => {
|
||||
await database.deleteDocument(ideasDatabaseId, ideasCollectionId, id);
|
||||
await fetch(); //Refetch ideas to ensure we have 10 items
|
||||
};
|
||||
const remove = async (id: string): Promise<void> => {
|
||||
await database.deleteDocument(ideasDatabaseId, ideasCollectionId, id);
|
||||
await fetch(); // Refetch ideas to ensure we have 10 items
|
||||
};
|
||||
|
||||
return {
|
||||
add,
|
||||
current,
|
||||
fetch,
|
||||
remove,
|
||||
};
|
||||
return {
|
||||
add,
|
||||
current,
|
||||
fetch,
|
||||
remove,
|
||||
};
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
Now we can call the `useIdeas()` composable from the home page.
|
||||
|
||||
@@ -17,7 +17,7 @@ The form need a text field for filling in the title, a textarea for the descript
|
||||
From the `components` directory, add the file `ÌdeasForm.vue` and add the following code.
|
||||
|
||||
```vue
|
||||
// components/IdeasForm.vue
|
||||
<!-- components/ideasForm.vue -->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
@@ -31,7 +31,7 @@ From the `components` directory, add the file `ÌdeasForm.vue` and add the follo
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Title"
|
||||
v-model="addIdeaData.title.value"
|
||||
name="title"
|
||||
/>
|
||||
</li>
|
||||
<!-- Description input field -->
|
||||
@@ -39,7 +39,7 @@ From the `components` directory, add the file `ÌdeasForm.vue` and add the follo
|
||||
<label class="label">Description</label>
|
||||
<textarea
|
||||
placeholder="Description"
|
||||
v-model="addIdeaData.description.value"
|
||||
name="description"
|
||||
/>
|
||||
</li>
|
||||
<button class="button" aria-label="Submit idea" type="submit">
|
||||
@@ -51,39 +51,26 @@ From the `components` directory, add the file `ÌdeasForm.vue` and add the follo
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
setup() {
|
||||
const ideas = useIdeas();
|
||||
const user = useUserSession();
|
||||
<script setup>
|
||||
const ideas = useIdeas();
|
||||
const user = useUserSession();
|
||||
|
||||
// Store input field values
|
||||
const addIdeaData = {
|
||||
title: ref(""),
|
||||
description: ref(""),
|
||||
};
|
||||
const handleAddIdea = async (event) => {
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
|
||||
const handleAddIdea = async () => {
|
||||
// Extract the values from the refs and add userId
|
||||
const postIdeaData = {
|
||||
userId: user.current.value.userId,
|
||||
title: addIdeaData.title.value,
|
||||
description: addIdeaData.title.value,
|
||||
};
|
||||
await ideas.add(postIdeaData);
|
||||
addIdeaData.title.value = ""; // Clear the title textfield
|
||||
addIdeaData.description.value = ""; // Clear the description textarea
|
||||
};
|
||||
|
||||
return {
|
||||
addIdeaData,
|
||||
handleAddIdea,
|
||||
ideas,
|
||||
};
|
||||
},
|
||||
// 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'),
|
||||
};
|
||||
</script>
|
||||
|
||||
await ideas.add(postIdeaData);
|
||||
|
||||
form.reset(); // Clear the form
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
Next, add the component to the page `pages/index.vue` by auto-importing it in the `<template>`tag.
|
||||
@@ -97,12 +84,12 @@ Add the following code to the `index.vue`page to conditionally render the form a
|
||||
Overwrite the contents of `pages/index.vue` with the following code.
|
||||
|
||||
```vue
|
||||
// pages/index.vue
|
||||
<!-- pages/index.vue -->
|
||||
|
||||
<template>
|
||||
<div class="u-max-width-650" style="margin: 0 auto;">
|
||||
<!-- Idea form component for logged in users -->
|
||||
<section v-if="user.isLoggedIn.value === true" class="card u-margin-32">
|
||||
<section v-if="user.current.value" class="card u-margin-32">
|
||||
<IdeasForm />
|
||||
</section>
|
||||
|
||||
@@ -114,27 +101,20 @@ Overwrite the contents of `pages/index.vue` with the following code.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<IdeasList />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
setup() {
|
||||
const user = useUserSession();
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
<script setup>
|
||||
// Access user composable function
|
||||
const user = useUserSession();
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
article.box {
|
||||
background-color: hsl(var(--color-neutral-0));
|
||||
}
|
||||
article.box {
|
||||
background-color: hsl(var(--color-neutral-0));
|
||||
}
|
||||
</style>
|
||||
|
||||
````
|
||||
|
||||
# Ideas list {% #ideas-list %}
|
||||
@@ -150,62 +130,56 @@ We help them do that by adding a delete button in the top right corner of the id
|
||||
Add the file `IdeasList`from the `componenents` directory and insert the following code:
|
||||
|
||||
```vue
|
||||
// componenents/IdeasList.vue
|
||||
<!-- componenents/ideasList.vue -->
|
||||
|
||||
<template>
|
||||
<section class="u-margin-32">
|
||||
<article class="card">
|
||||
<h4 class="heading-level-4">Latest Ideas</h4>
|
||||
<ul class="u-margin-block-start-8">
|
||||
<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>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
setup() {
|
||||
const ideas = useIdeas();
|
||||
const user = useUserSession();
|
||||
<script setup>
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
// Get ideas on page load
|
||||
onMounted(() => {
|
||||
ideas.fetch();
|
||||
});
|
||||
return { ideas, user };
|
||||
},
|
||||
};
|
||||
const ideas = useIdeas();
|
||||
const user = useUserSession();
|
||||
|
||||
console.log(ideas.current.value);
|
||||
|
||||
// Get ideas on page load
|
||||
onMounted(() => {
|
||||
ideas.fetch();
|
||||
});
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
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
|
||||
<!-- pages/index.vue -->
|
||||
|
||||
<template>
|
||||
<div class="u-max-width-650" style="margin: 0 auto;">
|
||||
|
||||
Reference in New Issue
Block a user