Skip to content

Commit

Permalink
Adding tailwind, next-auth and basic admin pages.
Browse files Browse the repository at this point in the history
  • Loading branch information
xaviemirmon committed Apr 22, 2024
1 parent 2f392ea commit ef83b07
Show file tree
Hide file tree
Showing 28 changed files with 3,799 additions and 345 deletions.
5 changes: 5 additions & 0 deletions .docksal/docksal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ services:
- DRUPAL_URI=http://web/
- BACKEND_STARTER
- FRONTEND_STARTER
- NEXT_HOST
# Keep cli running idle
command: ["tail", "-f", "/dev/null"]

Expand All @@ -40,6 +41,7 @@ services:
environment:
- APACHE_DOCUMENTROOT=/var/www/backend/starters/${BACKEND_STARTER}/${DOCROOT}
- APACHE_FCGI_HOST_PORT=php:9000
- NEXT_HOST
depends_on:
- php

Expand All @@ -56,6 +58,7 @@ services:
- BACKEND_STARTER
- FRONTEND_STARTER
- VIRTUAL_HOST
- NEXT_HOST
depends_on:
- cli # cli has to start first to avoid race conditions in startup scripts between cli, php and preview

Expand Down Expand Up @@ -87,6 +90,8 @@ services:
- DRUPAL_PREVIEW_SECRET
- NEXT_REVALIDATE_SECRET
- VIRTUAL_HOST
- NEXTAUTH_SECRET
- NEXTAUTH_URL
working_dir: /var/www/frontend
# Optionally run in debug mode by setting PREVIEW_START_COMMAND to "npm run debug"
command: [ "bash", "-lc", "yarn workspace ${FRONTEND_STARTER} dev" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use Drupal\Core\Link;
* Implements hook_entity_operation_alter().
*/
function powerstack_page_builder_entity_operation_alter(array &$operations, \Drupal\Core\Entity\EntityInterface $entity) {
if ($entity->getEntityTypeId() === 'node' && getenv('NEXT_HOST'),) {
if ($entity->getEntityTypeId() === 'node' && getenv('NEXT_HOST')) {
// Adding an external operation
$operations['edit'] = [
'title' => t('Edit'),
Expand Down
28 changes: 14 additions & 14 deletions backend/starters/development/web/sites/default/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@
* $settings['hash_salt'] = file_get_contents('/home/example/salt.txt');
* @endcode
*/
$settings['hash_salt'] = '5-8ABPetu2c1b__bq3kiQJZB_5P1MfkN9ykpym1zvksi3DIsmSBb2lIrv8dyMo8Td1xJTzGtFg';
$settings['hash_salt'] = 'r0makX3ogmbVAhTfl1JkFeU5YyND0K-Cwy12DIWjnKnU8Xdp0Ded1V2CwfEFS8KCGZIdo59cSw';

/**
* Deployment identifier.
Expand Down Expand Up @@ -772,19 +772,19 @@
* Keep this code block at the end of this file to take full effect.
*/


// Docksal DB connection settings.
$databases['default']['default'] = [
'database' => getenv('MYSQL_DB'),
'username' => getenv('MYSQL_USER'),
'password' => getenv('MYSQL_PASSWORD'),
'host' => getenv('MYSQL_HOST'),
'port' => getenv('MYSQL_PORT'),
'driver' => 'mysql',
];

if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}

$settings['config_sync_directory'] = '../config';
$databases['default']['default'] = array (
'database' => 'default',
'username' => 'user',
'password' => 'user',
'prefix' => '',
'host' => 'backend_db',
'port' => '3306',
'isolation_level' => 'READ COMMITTED',
'driver' => 'mysql',
'namespace' => 'Drupal\\mysql\\Driver\\Database\\mysql',
'autoload' => 'core/modules/mysql/src/Driver/Database/mysql/',
);
$settings['config_sync_directory'] = 'sites/default/files/config_9nu_OfPlmXpYfp2Zovw4sdEMmr9h0PlRLHyfxji7kXdMmRp4M5ET9aZecyQ53wqQSHVFMPpoUA/sync';
86 changes: 86 additions & 0 deletions frontend/starters/development/app/admin/content/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import Link from "next/link"

import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"

export default function Dashboard() {
return (
<div className="flex min-h-screen w-full flex-col">
<main className="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 bg-muted/40 p-4 md:gap-8 md:p-10">
<div className="mx-auto grid w-full max-w-6xl gap-2">
<h1 className="text-3xl font-semibold">Settings</h1>
</div>
<div className="mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]">
<nav
className="grid gap-4 text-sm text-muted-foreground" x-chunk="dashboard-04-chunk-0"
>
<Link href="#" className="font-semibold text-primary">
General
</Link>
<Link href="#">Security</Link>
<Link href="#">Integrations</Link>
<Link href="#">Support</Link>
<Link href="#">Organizations</Link>
<Link href="#">Advanced</Link>
</nav>
<div className="grid gap-6">
<Card x-chunk="dashboard-04-chunk-1">
<CardHeader>
<CardTitle>Store Name</CardTitle>
<CardDescription>
Used to identify your store in the marketplace.
</CardDescription>
</CardHeader>
<CardContent>
<form>
<Input placeholder="Store Name" />
</form>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Button>Save</Button>
</CardFooter>
</Card>
<Card x-chunk="dashboard-04-chunk-2">
<CardHeader>
<CardTitle>Plugins Directory</CardTitle>
<CardDescription>
The directory within your project, in which your plugins are
located.
</CardDescription>
</CardHeader>
<CardContent>
<form className="flex flex-col gap-4">
<Input
placeholder="Project Name"
defaultValue="/content/plugins"
/>
<div className="flex items-center space-x-2">
<Checkbox id="include" defaultChecked />
<label
htmlFor="include"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Allow administrators to change the directory.
</label>
</div>
</form>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Button>Save</Button>
</CardFooter>
</Card>
</div>
</div>
</main>
</div>
)
}
68 changes: 68 additions & 0 deletions frontend/starters/development/app/admin/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

import Image from "next/image"
import Link from "next/link"

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function Dashboard() {
return (
<div className="w-full lg:grid lg:min-h-[600px] lg:grid-cols-2 xl:min-h-[800px]">
<div className="flex items-center justify-center py-12">
<div className="mx-auto grid w-[350px] gap-6">
<div className="grid gap-2 text-center">
<h1 className="text-3xl font-bold">Login</h1>
<p className="text-balance text-muted-foreground">
Enter your email below to login to your account
</p>
</div>
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
<Link
href="/forgot-password"
className="ml-auto inline-block text-sm underline"
>
Forgot your password?
</Link>
</div>
<Input id="password" type="password" required />
</div>
<Button type="submit" className="w-full">
Login
</Button>
<Button variant="outline" className="w-full">
Login with Google
</Button>
</div>
<div className="mt-4 text-center text-sm">
Don&apos;t have an account?{" "}
<Link href="#" className="underline">
Sign up
</Link>
</div>
</div>
</div>
<div className="hidden bg-muted lg:block">
<Image
src="/placeholder.svg"
alt="Image"
width="1920"
height="1080"
className="h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
/>
</div>
</div>
)
}
114 changes: 114 additions & 0 deletions frontend/starters/development/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import jwt_decode from "jwt-decode"

const handler = NextAuth({
providers: [
CredentialsProvider({
name: "Drupal",
credentials: {
username: { label: "Username", type: "text", placeholder: "Username" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const formData = new URLSearchParams()
formData.append("grant_type", "password")
formData.append("client_id", process.env.NEXT_PUBLIC_DRUPAL_CLIENT_ID)
formData.append("client_secret", process.env.NEXT_PUBLIC_DRUPAL_CLIENT_SECRET)
formData.append("username", credentials.username)
formData.append("password", credentials.password)

// Get access token from Drupal.
const response = await fetch(
`${process.env.NEXT_PUBLIC_DRUPAL_HOST}/oauth/token`,
{
method: "POST",
body: formData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}
)

if (!response.ok) {
return null
}

return await response.json()
},
}),
],
callbacks: {
async jwt({ token, user, account }) {
if (account && user) {
token.accessToken = user.access_token
token.accessTokenExpires =
Date.now() + (user.expires_in as number) * 1000
token.refreshToken = user.refresh_token
}

// If token has not expired, return it,
if (Date.now() < token.accessTokenExpires) {
return token
}

// Otherwise, refresh the token.
return refreshAccessToken(token)
},
async session({ session, token }) {
if (token?.accessToken) {
session.accessToken = token.accessToken
const decoded = jwt_decode<{ email: string; username: string }>(
token.accessToken as string
)
session.user.email = decoded.email
session.user.name = decoded.username
session.error = token.error
}
return session
}
},
})

// Helper to obtain a new access_token from a refresh token.
async function refreshAccessToken(token) {
try {
const formData = new URLSearchParams()

formData.append("grant_type", "refresh_token")
formData.append("client_id", process.env.NEXT_PUBLIC_DRUPAL_CLIENT_ID)
formData.append("client_secret", process.env.NEXT_PUBLIC_DRUPAL_CLIENT_SECRET)
formData.append("refresh_token", token.refreshToken)

const response = await fetch(
`${process.env.NEXT_PUBLIC_DRUPAL_HOST}/oauth/token`,
{
method: "POST",
body: formData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}
)

const data = await response.json()

if (!response.ok) {
throw new Error()
}

return {
...token,
accessToken: data.access_token,
accessTokenExpires: Date.now() + data.expires_in * 1000,
refreshToken: data.refresh_token ?? token.refreshToken,
}
} catch (error) {
return {
...token,
error: "RefreshAccessTokenError",
}
}
}

export { handler as GET, handler as POST }
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { drupal } from "@/lib/drupal";
import { drupalFieldPrefix } from "@powerstack/utils";
import toast from "react-hot-toast";
import Link from "next/link";
import { Header } from "@/components/admin/Header/Header";

export function Client({ path, data }: { path: string; data: Data }) {
const backendUrl = process.env.NEXT_PUBLIC_DRUPAL_HOST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export default async function Page({
}: {
params: { puckPath: string[] };
}) {

const path = `/${puckPath.join("/")}`;

let node
Expand Down

0 comments on commit ef83b07

Please sign in to comment.