Skip to content

Commit

Permalink
Merge pull request #59 from emiliosheinz/dev
Browse files Browse the repository at this point in the history
chore: upload to prod
  • Loading branch information
emiliosheinz authored May 20, 2024
2 parents 9df7de9 + e2489a4 commit dc1e4cf
Show file tree
Hide file tree
Showing 24 changed files with 602 additions and 291 deletions.
17 changes: 12 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ DATABASE_URL="postgresql://postgres:password@localhost:5432/sos-pet"

# Next Auth
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="nextauthsecret"
NEXTAUTH_SECRET="nextauth-secret"

# Next Auth Providers
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
# Next Auth Google Sign-In
GOOGLE_CLIENT_ID="google-client-id"
GOOGLE_CLIENT_SECRET="google-client-secret"
# Next Auth Email magic link Sign-In
# https://resend.com/changelog/smtp-service
EMAIL_HOST="smtp.resend.com"
EMAIL_PORT="465"
EMAIL_USER="resend"
EMAIL_PASSWORD="resend-api-key"
EMAIL_FROM="[email protected]"

# Google Maps
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=""
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY="google-maps-api-key"

# Docker related variables, not used on Next.js
POSTGRES_DB="sos-pet"
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/lint-type-check-and-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
jobs:
lint-type-check-and-build:
runs-on: ubuntu-latest
name: Lint, Type Check, and Build
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -20,7 +21,10 @@ jobs:
cache: "yarn"

- name: Setup .env file
run: echo "${{ secrets.DOT_ENV_FILE_CONTENT }}" > .env
# All the environment variables in .env.example are invalid,
# therefore the application will not be functional. But, it is enough
# for linting, type checking, and building.
run: cat .env.example > .env

- name: Install dependencies
run: yarn
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ Com o SOS Pet, as pessoas que resgatam animais de enchentes podem rapidamente en
1. Instale as dependências
1. Crie um arquivo `.env` baseando se no `.env.example`
1. Assegure se de preencher todas as variáveis ambiente.
1. Suba o baco de dados: `docker compose up -d`
1. Suba o baco de dados: `docker-compose up -d`
1. Rode as migrations: `npx prisma migrate dev`
1. Rode o projeto com o script `dev` disponível no `package.json`
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"next": "^14.2.1",
"next-auth": "^4.24.6",
"next-themes": "^0.3.0",
"nodemailer": "^6.9.13",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.51.4",
Expand All @@ -55,6 +56,7 @@
"devDependencies": {
"@types/eslint": "^8.56.2",
"@types/node": "^20.11.20",
"@types/nodemailer": "^6.4.15",
"@types/react": "^18.2.57",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^7.1.1",
Expand Down
2 changes: 2 additions & 0 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export default function About() {
transformar um momento de crise em uma oportunidade para fazer a
diferença na vida de um animal. Juntos, podemos salvar vidas e
construir um futuro mais seguro para nossos amigos de quatro patas.
Além disso, esse projeto tem o código fonte aberto e disponível para colaboração:
<a href="https://github.com/emiliosheinz/sos-pet" target="blank"> https://github.com/emiliosheinz/sos-pet</a>
</p>
</div>
</div>
Expand Down
11 changes: 0 additions & 11 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,13 @@
import { Card } from "~/components/card/";
import { SearchInput } from "~/components/search-input";
import { api } from "~/trpc/react";
import { Filters } from "~/components/filters";
import Fuse from "fuse.js";
import { useMemo } from "react";
import { Skeleton } from "~/components/ui/skeleton";
import { useDebouncedState } from "~/hooks/use-debouced-state";
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
import { FiInfo } from "react-icons/fi";

const menus = [
{
label: "Disponibilidade",
items: [
{ label: "Com vagas", checked: true },
{ label: "Sem vagas", checked: false },
],
},
];

export default function Home() {
const { data, isLoading } = api.shelter.findAll.useQuery();
const [searchTerm, setSearchTerm] = useDebouncedState("", 300);
Expand Down
84 changes: 84 additions & 0 deletions src/app/signin/_components/AuthenticationProviders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use client";

import { getProviders } from "next-auth/react";
import { useEffect, useMemo, useState } from "react";
import { SignInProviderButton } from "./SignInProviderButton";
import { EmailProviderForm } from "./EmailProviderForm";
import { Loader2 } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
import { FiAlertTriangle } from "react-icons/fi";

type GetProvidersState = "idle" | "loading" | "success" | "error";

type AuthenticationProvidersProps = {
callbackUrl?: string;
};

/**
* Prevents multiple calls to getProviders during one session
*/
let cachedProviders: Awaited<ReturnType<typeof getProviders>> = null;
async function getCachedProviders() {
if (!!cachedProviders) return cachedProviders;
return (cachedProviders = await getProviders());
}

export function AuthenticationProviders({
callbackUrl,
}: AuthenticationProvidersProps) {
const [providers, setProviders] =
useState<Awaited<ReturnType<typeof getProviders>>>();
const [getProvidersState, setGetProvidersState] =
useState<GetProvidersState>("idle");

useEffect(() => {
setGetProvidersState("loading");
getCachedProviders()
.then((providers) => {
setProviders(providers);
setGetProvidersState("success");
})
.catch(() => {
setGetProvidersState("error");
});
}, []);

const [emailProvider, otherProviders] = useMemo(() => {
if (!providers) return [null, null];
const email = providers.email;
const other = Object.values(providers).filter(
(provider) => provider.id !== "email",
);
return [email, other];
}, [providers]);

if (["loading", "idle"].includes(getProvidersState)) {
return <Loader2 className="mt-10 size-8 animate-spin" />;
}

if (getProvidersState === "error") {
return (
<Alert variant="destructive">
<FiAlertTriangle className="h-4 w-4" />
<AlertTitle>Erro ao carregar provedores de login</AlertTitle>
<AlertDescription>
<span>Por favor, entre em contato com o nosso suporte em&nbsp;</span>
<a href="mailto:[email protected]">[email protected]</a>
</AlertDescription>
</Alert>
);
}

return (
<div className="mt-5 w-full">
{otherProviders?.map((provider) => (
<SignInProviderButton
key={provider.id}
provider={provider}
callbackUrl={callbackUrl ?? "/"}
/>
))}
{!!emailProvider && <EmailProviderForm />}
</div>
);
}
72 changes: 72 additions & 0 deletions src/app/signin/_components/EmailProviderForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { Loader2 } from "lucide-react";
import { signIn } from "next-auth/react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "~/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "~/components/ui/form";
import { Input } from "~/components/ui/input";

const formSchema = z.object({
email: z.string().email("Por favor, insira um e-mail válido"),
});

export function EmailProviderForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: { email: "" },
});
const [isLoading, setIsLoading] = useState(false);

const onSubmit = async ({ email }: z.infer<typeof formSchema>) => {
setIsLoading(true);
await signIn("email", { email });
setIsLoading(false);
};

return (
<Form {...form}>
<form
className="mt-5 flex flex-col gap-5"
onSubmit={form.handleSubmit(onSubmit)}
>
<div className="relative my-5">
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform bg-white pb-1 text-lg tracking-widest text-neutral-500">
ou
</span>
<hr />
</div>
<FormField
name="email"
control={form.control}
disabled={isLoading}
render={({ field }) => (
<FormItem>
<FormControl>
<Input placeholder="[email protected]" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? (
<Loader2 className="animate-spin" />
) : (
"Entrar com e-mail"
)}
</Button>
</form>
</Form>
);
}
30 changes: 30 additions & 0 deletions src/app/signin/_components/SignInProviderButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client";

import { type ClientSafeProvider, signIn } from "next-auth/react";
import Image from "next/image";

type SignInProviderButtonProps = {
provider: ClientSafeProvider;
callbackUrl: string;
};

export function SignInProviderButton({
provider,
callbackUrl,
}: SignInProviderButtonProps) {
return (
<button
type="button"
className="inline-flex h-10 w-full items-center justify-center whitespace-nowrap rounded-md border border-neutral-200 bg-white px-4 py-2 text-sm font-medium text-neutral-900 ring-offset-white transition-colors hover:bg-neutral-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
onClick={() => signIn(provider.id, { callbackUrl })}
>
<Image
src={`/${provider.id}.svg`}
alt={`Icone de ${provider.name}`}
width={32}
height={32}
/>
Entrar com {provider.name}
</button>
);
}
30 changes: 0 additions & 30 deletions src/app/signin/_components/SigninProviderButton.tsx

This file was deleted.

9 changes: 9 additions & 0 deletions src/app/signin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type PropsWithChildren } from "react";

export default function SignInVerifyLayout({ children }: PropsWithChildren) {
return (
<div className="m-auto flex w-full max-w-lg flex-col items-center justify-center gap-5 p-5 pt-28">
{children}
</div>
);
}
Loading

0 comments on commit dc1e4cf

Please sign in to comment.