Skip to content

I18n and pwa #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This is a comprehensively updated fork of [Sebastián Ramírez's](https://github
- [Deployment for production](./docs/deployment-guide.md)
- [Authentication and magic tokens](./docs/authentication-guide.md)
- [More details](#more-details)
- [Help needed](#help-needed)
- [Release notes](#release-notes)
- [License](#license)

Expand Down Expand Up @@ -60,6 +61,8 @@ This FastAPI, PostgreSQL, Neo4j & Nuxt 3 repo will generate a complete web appli
- **Form validation** with [Vee-Validate 4](https://vee-validate.logaretm.com/v4/).
- **State management** with [Pinia](https://pinia.vuejs.org/), and persistance with [Pinia PersistedState](https://prazdevs.github.io/pinia-plugin-persistedstate/).
- **CSS and templates** with [TailwindCSS](https://tailwindcss.com/), [HeroIcons](https://heroicons.com/), and [HeadlessUI](https://headlessui.com/).
- **Internationalisation** with [@nuxt/i18n](https://nuxt.com/modules/i18n).
- **PWA support** with [Vite PWA plugin](https://vite-pwa-org.netlify.app/frameworks/nuxt.html).
- **PostgreSQL** database.
- **PGAdmin** for PostgreSQL database management.
- **Celery** worker that can import and use models and code from the rest of the backend selectively.
Expand All @@ -84,17 +87,24 @@ This current release (August 2023) is for FastAPI version 0.99 and is the last b

To align with [Inboard](https://inboard.bws.bio/), Poetry has been deprecated in favour of [Hatch](https://hatch.pypa.io/latest/). This will also, hopefully, sort out some Poetry-related Docker build errors.

You will also find an initial implementation of internationalisation using [@nuxt/i18n](https://nuxt.com/modules/i18n). This is - at this time - a release candidate, so please do update and check their documentation for any changes. The [Vite PWA plugin](https://vite-pwa-org.netlify.app/frameworks/nuxt.html) is also included, along with a Node CLI for generating all necessary app icons. You will see links and notes to this in the [nuxt.config.ts](./{{cookiecutter.project_slug}}/frontend/nuxt.config.ts) file.

## Help needed

The tests are broken and it would be great if someone could take that on. Other potential roadmap items:

- Translation: docs are all in English and it would be great if those could be in other languages.
- Internationalisation: I am working on adding [nuxt/i18n](https://v8.i18n.nuxtjs.org/), but the Nuxt3 version is still pre-release.
- PWA: Would be good to review the Vite [PWA](https://vite-pwa-org.netlify.app/) plugin.
- Internationalisation: [nuxt/i18n](https://v8.i18n.nuxtjs.org/) is added, but the sample pages are not all translated.
- Code review and optimisation: both the front- and backend stacks have seen some big generational changes, so would be good to have more eyes on the updates to this stack.

## Release Notes

See notes and [releases](https://github.com/whythawk/full-stack-fastapi-postgresql/releases).
### 0.7.5

- Updates to `frontend`, [#37](https://github.com/whythawk/full-stack-fastapi-postgresql/pull/37) by @turukawa:
- `@nuxtjs/i18n` for internationalisation, along with language selection component.
- `@vite-pwa/nuxt` along with button components for install and refreshing the app and service workers, and a CLI icon generator.
- `@nuxtjs/robots` for simple control of `robots.txt` permissions from `nuxt.config.ts`.

### 0.7.4
- Updates: Complete update of stack to latest long-term releases. [#35](https://github.com/whythawk/full-stack-fastapi-postgresql/pull/35) by @turukawa, review by @br3ndonland
Expand Down
23 changes: 15 additions & 8 deletions {{cookiecutter.project_slug}}/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
FROM node:18.17.0 AS build
ENV NODE_ENV=development NITRO_HOST=${NUXT_HOST:-0.0.0.0} NITRO_PORT=${NUXT_PORT:-3000} NUXT_TELEMETRY_DISABLED=1
FROM node:18.17 AS build
ENV NODE_ENV=development APP_ENV=development NITRO_HOST=${NUXT_HOST:-0.0.0.0} NITRO_PORT=${NUXT_PORT:-3000} NUXT_TELEMETRY_DISABLED=1
# ENV PATH /frontend/node_modules/.bin:$PATH
COPY . /frontend
WORKDIR /frontend
RUN yarn install --frozen-lockfile --network-timeout 100000 --non-interactive
RUN yarn build --standalone
EXPOSE ${NUXT_PORT}

FROM build AS run-dev
ENTRYPOINT ["yarn"]
CMD ["dev"]
# ENTRYPOINT ["yarn"]
CMD ["yarn", "dev"]

FROM build AS run-start
ENV NODE_ENV=production
ENV NODE_ENV=production APP_ENV=production
ENTRYPOINT ["yarn"]
CMD ["start"]

FROM node:18.17.0-alpine AS run-minimal
FROM node:18.17-alpine AS run-minimal
ARG NUXT_VERSION=^3.5.0
ARG NUXT_CONTENT_VERSION=^2.4.3
ARG TAILWINDCSS_VERSION=^3.2.1
Expand All @@ -32,19 +33,25 @@ ARG VEE_VERSION=^4.7.3
ARG VEE_INT_VERSION=^4.7.3
ARG VEE_RULES_VERSION=^4.7.3
ARG QR_CODE_VERSION=^3.3.3
ENV NODE_ENV=production NITRO_HOST=${NUXT_HOST:-0.0.0.0} NITRO_PORT=${NUXT_PORT:-3000} NUXT_TELEMETRY_DISABLED=1
ARG I18N_VERSION=^8.0.0-beta.13
ARG NUXT_ROBOTS_VERSION=^3.0.0
ARG VITE_PWA_NUXT_VERSION=^0.1.0
ENV NODE_ENV=production APP_ENV=production NITRO_HOST=${NUXT_HOST:-0.0.0.0} NITRO_PORT=${NUXT_PORT:-3000} NUXT_TELEMETRY_DISABLED=1
WORKDIR /frontend
RUN yarn add nuxt@${NUXT_VERSION} @nuxt/content@${NUXT_CONTENT_VERSION} tailwindcss@${TAILWINDCSS_VERSION} autoprefixer@${AUTOPREFIXER_VERSION} postcss@${POSTCSS_VERSION} @tailwindcss/aspect-ratio@${ASPECT_RATIO_VERSION} @tailwindcss/forms@${FORMS_VERSION} @tailwindcss/typography@${TYPOGRAPHY_VERSION} @headlessui/vue@${HEADLESSUI_VERSION} @heroicons/vue@${HEROICONS_VERSION} @pinia/nuxt@${PINIA_VERSION} @pinia-plugin-persistedstate/nuxt${PINIA_PERSISTED_VERSION} vee-validate@${VEE_VERSION} @vee-validate/i18n${VEE_INT_VERSION} @vee-validate/rules${VEE_RULES_VERSION} qrcode.vue${QR_CODE_VERSION}
RUN yarn add nuxt@${NUXT_VERSION} @nuxt/content@${NUXT_CONTENT_VERSION} tailwindcss@${TAILWINDCSS_VERSION} autoprefixer@${AUTOPREFIXER_VERSION} postcss@${POSTCSS_VERSION} @tailwindcss/aspect-ratio@${ASPECT_RATIO_VERSION} @tailwindcss/forms@${FORMS_VERSION} @tailwindcss/typography@${TYPOGRAPHY_VERSION} @headlessui/vue@${HEADLESSUI_VERSION} @heroicons/vue@${HEROICONS_VERSION} @pinia/nuxt@${PINIA_VERSION} @pinia-plugin-persistedstate/nuxt${PINIA_PERSISTED_VERSION} vee-validate@${VEE_VERSION} @vee-validate/i18n${VEE_INT_VERSION} @vee-validate/rules${VEE_RULES_VERSION} qrcode.vue${QR_CODE_VERSION} @nuxtjs/i18n${I18N_VERSION} @nuxtjs/robots${NUXT_ROBOTS_VERSION} @vite-pwa/nuxt${VITE_PWA_NUXT_VERSION}
COPY --from=build /app/.nuxt ./.nuxt
COPY --from=build /app/api ./api
COPY --from=build /app/assets ./assets
COPY --from=build /app/components ./components
COPY --from=build /app/config ./config
COPY --from=build /app/content ./content
COPY --from=build /app/interfaces ./interfaces
COPY --from=build /app/layouts ./layouts
COPY --from=build /app/locales ./locales
COPY --from=build /app/middleware ./middleware
COPY --from=build /app/pages ./pages
COPY --from=build /app/plugins ./plugins
COPY --from=build /app/public ./public
COPY --from=build /app/static ./static
COPY --from=build /app/stores ./stores
COPY --from=build /app/utilities ./utilities
Expand Down
17 changes: 17 additions & 0 deletions {{cookiecutter.project_slug}}/frontend/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,23 @@ export const apiAuth = {
}
)
},
async requestValidationEmail(token: string) {
return await useFetch<IMsg>(`${apiCore.url()}/users/send-validation-email`,
{
method: "POST",
headers: apiCore.headers(token)
}
)
},
async validateEmail(token: string, validation: string) {
return await useFetch<IMsg>(`${apiCore.url()}/users/validate-email`,
{
method: "POST",
body: { validation },
headers: apiCore.headers(token)
}
)
},
// ADMIN USER MANAGEMENT
async getAllUsers(token: string) {
return await useFetch<IUserProfile[]>(`${apiCore.url()}/users/all`,
Expand Down
17 changes: 13 additions & 4 deletions {{cookiecutter.project_slug}}/frontend/app.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
<template>
<div class="h-full">
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
<div>
<Head>
<Link rel="icon" href="/favicon.ico" sizes="any" />
<Link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<Link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" />
</Head>
<div class="h-full">
<VitePwaManifest />
<NuxtLoadingIndicator />
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
</div>
</div>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
</template>


<script setup>
<script setup lang="ts">
import { BellIcon } from "@heroicons/vue/24/outline"
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
<!-- Profile dropdown -->
<Menu as="div" class="relative ml-3">
<div v-if="!authStore.loggedIn">
<NuxtLink
<LocaleLink
to="/login"
class="rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2"
>
<ArrowLeftOnRectangleIcon class="block h-6 w-6" />
</NuxtLink>
</LocaleLink>
</div>
<div v-else>
<MenuButton class="flex rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2">
Expand All @@ -22,11 +22,11 @@
:key="`nav-${i}`"
v-slot="{ active }"
>
<NuxtLink
<LocaleLink
:to="nav.to"
:class="[active ? 'bg-gray-100' : '', 'block px-4 py-2 text-sm text-gray-700']"
>{{ nav.name }}
</NuxtLink>
</LocaleLink>
</MenuItem>
<MenuItem v-slot="{ active }">
<a
Expand All @@ -42,7 +42,7 @@
</template>


<script setup>
<script setup lang="ts">
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue"
import { ArrowLeftOnRectangleIcon } from "@heroicons/vue/24/outline"
import { useAuthStore } from "@/stores"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="mx-auto max-w-md overflow-hidden py-12 px-4 sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8">
<nav class="-mx-5 -my-2 flex flex-wrap justify-center" aria-label="Footer">
<div v-for="item in footerNavigation.main" :key="item.name" class="px-5 py-2">
<NuxtLink :to="item.to" class="text-base text-gray-400 hover:text-gray-300">{{ item.name }}</NuxtLink>
<LocaleLink :to="item.to" class="text-base text-gray-400 hover:text-gray-300">{{ t(item.name) }}</LocaleLink>
</div>
</nav>
<div class="mt-8 flex justify-center space-x-6">
Expand All @@ -12,19 +12,23 @@
<component :is="item.icon" class="h-6 w-6" aria-hidden="true" />
</a>
</div>
<p class="mt-8 text-center text-base text-gray-400">&copy; 2022 {{ siteName }}. All rights reserved.</p>
<div class="flex justify-between">
<p class="mt-8 text-center text-base text-gray-400">&copy; 2022 {{ t(siteName) }}. {{ t("footer.rights") }}</p>
<LocaleDropdown />
</div>
</div>
</footer>
</template>

<script setup lang="ts">
const siteName: String = "Your Company, Inc"
const { t } = useI18n()
const siteName: string = "common.title"

const footerNavigation = {
main: [
{ name: "About", to: "/about" },
{ name: "Authentication", to: "/authentication" },
{ name: "Blog", to: "/blog" },
{ name: "nav.about", to: "/about" },
{ name: "nav.authentication", to: "/authentication" },
{ name: "nav.blog", to: "/blog" },
],
social: [
{
Expand Down Expand Up @@ -57,4 +61,29 @@ const footerNavigation = {
},
],
}
</script>
</script>

<style>
.pwa-toast {
position: fixed;
right: 0;
bottom: 0;
margin: 16px;
padding: 12px;
border: 1px solid #8885;
border-radius: 4px;
z-index: 1;
text-align: left;
box-shadow: 3px 4px 5px 0 #8885;
}
.pwa-toast .message {
margin-bottom: 8px;
}
.pwa-toast button {
border: 1px solid #8885;
outline: none;
margin-right: 5px;
border-radius: 2px;
padding: 3px 10px;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,52 @@
</DisclosureButton>
</div>
<div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<NuxtLink to="/" class="flex flex-shrink-0 items-center">
<LocaleLink to="/" class="flex flex-shrink-0 items-center">
<img class="block h-8 w-auto lg:hidden" src="https://tailwindui.com/img/logos/mark.svg?color=rose&shade=600" alt="Your Company" />
<img class="hidden h-8 w-auto lg:block" src="https://tailwindui.com/img/logos/mark.svg?color=rose&shade=600" alt="Your Company" />
</NuxtLink>
</LocaleLink>
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
<NuxtLink
<LocaleLink
v-for="(nav, i) in navigation"
:key="`nav-${i}`"
:to="nav.to"
class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900 hover:text-rose-500"
>{{ nav.name }}</NuxtLink>
>{{ t(nav.name) }}</LocaleLink>
</div>
</div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<PwaBadge />
<PwaInstallPrompt />
<AlertsButton />
<AuthenticationNavigation />
</div>
</div>
</div>

<DisclosurePanel class="sm:hidden">
<div class="space-y-1 pt-2 pb-4">
<!-- Current: "bg-rose-50 border-rose-500 text-rose-700", Default: "border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700" -->
<NuxtLink
<LocaleLink
v-for="(nav, i) in navigation"
:key="`nav-mobile-${i}`"
:to="nav.to"
class="block hover:border-l-4 hover:border-rose-500 hover:bg-rose-50 py-2 pl-3 pr-4 text-base font-medium text-rose-700">
{{ nav.name }}
</NuxtLink>
{{ t(nav.name) }}
</LocaleLink>
</div>
</DisclosurePanel>
</Disclosure>
</header>
</template>
<script setup>
import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/vue"
import { Bars3Icon, XMarkIcon } from "@heroicons/vue/24/outline"
</template>

<script setup lang="ts">
import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/vue"
import { Bars3Icon, XMarkIcon } from "@heroicons/vue/24/outline"

const navigation = [
{ name: "About", to: "/about" },
{ name: "Authentication", to: "/authentication" },
{ name: "Blog", to: "/blog" },
]
</script>
const { t } = useI18n()
const navigation = [
{ name: "nav.about", to: "/about" },
{ name: "nav.authentication", to: "/authentication" },
{ name: "nav.blog", to: "/blog" },
]
</script>
Loading