Almost working auth
This commit is contained in:
parent
6382b6c02b
commit
8f28d07051
69
database.go
69
database.go
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
var edgeCtx context.Context
|
var edgeCtx context.Context
|
||||||
var edgeClient *edgedb.Client
|
var edgeClient *edgedb.Client
|
||||||
var CurrentUser User
|
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID edgedb.UUID `edgedb:"id"`
|
ID edgedb.UUID `edgedb:"id"`
|
||||||
@ -74,22 +73,15 @@ func init() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user test@example.com
|
// TODO Change
|
||||||
var user User
|
edgeCtx = ctx
|
||||||
err = client.QuerySingle(ctx, `
|
var clientUUID edgedb.UUID
|
||||||
SELECT User { id, email, name, setting } FILTER .email = <str>$0 LIMIT 1
|
clientUUID, err = edgedb.ParseUUID("9323365e-0b09-11ef-8f41-c3575d386283")
|
||||||
`, &user, "test@example.com")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error in edgedb.QuerySingle: in init")
|
fmt.Println("Error in edgedb.ParseUUID: in init")
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
CurrentUser = user
|
edgeClient = client.WithGlobals(map[string]interface{}{"current_user_id": clientUUID})
|
||||||
|
|
||||||
fmt.Print("Current User: ")
|
|
||||||
fmt.Println(CurrentUser)
|
|
||||||
|
|
||||||
edgeCtx = ctx
|
|
||||||
edgeClient = client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLastArea() edgedb.UUID {
|
func getLastArea() edgedb.UUID {
|
||||||
@ -99,7 +91,7 @@ func getLastArea() edgedb.UUID {
|
|||||||
filter .conversation.name = 'Default' AND .conversation.user.id = <uuid>$0
|
filter .conversation.name = 'Default' AND .conversation.user.id = <uuid>$0
|
||||||
order by .position desc
|
order by .position desc
|
||||||
limit 1
|
limit 1
|
||||||
`, &inserted, CurrentUser.ID)
|
`, &inserted, getCurrentUserID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error in edgedb.QuerySingle: in getLastArea")
|
fmt.Println("Error in edgedb.QuerySingle: in getLastArea")
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -107,21 +99,43 @@ func getLastArea() edgedb.UUID {
|
|||||||
return inserted.id
|
return inserted.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCurrentUserID() edgedb.UUID {
|
||||||
|
var result User
|
||||||
|
err := edgeClient.QuerySingle(edgeCtx, "SELECT global currentUser LIMIT 1;", &result)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error in edgedb.QuerySingle: in getCurrentUserID")
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
return result.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkIfLogin() bool {
|
||||||
|
var result User
|
||||||
|
err := edgeClient.QuerySingle(edgeCtx, "SELECT global currentUser LIMIT 1;", &result)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error in edgedb.QuerySingle: in checkIfLogin")
|
||||||
|
fmt.Println(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func insertArea() edgedb.UUID {
|
func insertArea() edgedb.UUID {
|
||||||
// Insert a new area.
|
// Insert a new area.
|
||||||
var inserted struct{ id edgedb.UUID }
|
var inserted struct{ id edgedb.UUID }
|
||||||
err := edgeClient.QuerySingle(edgeCtx, `
|
err := edgeClient.QuerySingle(edgeCtx, `
|
||||||
WITH
|
WITH
|
||||||
positionVar := count((SELECT Area FILTER .conversation.name = 'Default' AND .conversation.user.id = <uuid>$0)) + 1
|
positionVar := count((SELECT Area FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser)) + 1
|
||||||
INSERT Area {
|
INSERT Area {
|
||||||
position := positionVar,
|
position := positionVar,
|
||||||
conversation := (
|
conversation := (
|
||||||
SELECT Conversation
|
SELECT Conversation
|
||||||
FILTER .name = 'Default' AND .user.id = <uuid>$0
|
FILTER .name = 'Default' AND .user = global currentUser
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`, &inserted, CurrentUser.ID)
|
`, &inserted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error in edgedb.QuerySingle: in insertArea")
|
fmt.Println("Error in edgedb.QuerySingle: in insertArea")
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -144,11 +158,11 @@ func insertUserMessage(content string) edgedb.UUID {
|
|||||||
),
|
),
|
||||||
conversation := (
|
conversation := (
|
||||||
SELECT Conversation
|
SELECT Conversation
|
||||||
FILTER .name = 'Default' AND .user.id = <uuid>$3
|
FILTER .name = 'Default' AND .user = global currentUser
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`, &inserted, "user", content, lastAreaID, CurrentUser.ID)
|
`, &inserted, "user", content, lastAreaID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error in edgedb.QuerySingle: in insertUserMessage")
|
fmt.Println("Error in edgedb.QuerySingle: in insertUserMessage")
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -167,16 +181,16 @@ func insertBotMessage(content string, selected bool, model string) edgedb.UUID {
|
|||||||
selected := <bool>$3,
|
selected := <bool>$3,
|
||||||
conversation := (
|
conversation := (
|
||||||
SELECT Conversation
|
SELECT Conversation
|
||||||
FILTER .name = 'Default' AND .user.id = <uuid>$4
|
FILTER .name = 'Default' AND .user = global currentUser
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
),
|
),
|
||||||
area := (
|
area := (
|
||||||
SELECT Area
|
SELECT Area
|
||||||
FILTER .id = <uuid>$5
|
FILTER .id = <uuid>$4
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`, &inserted, "bot", model, content, selected, CurrentUser.ID, lastAreaID)
|
`, &inserted, "bot", model, content, selected, lastAreaID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error in edgedb.QuerySingle: in insertBotMessage")
|
fmt.Println("Error in edgedb.QuerySingle: in insertBotMessage")
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -185,6 +199,11 @@ func insertBotMessage(content string, selected bool, model string) edgedb.UUID {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAllMessages() []Message {
|
func getAllMessages() []Message {
|
||||||
|
// If no CurrentUser, return an empty array
|
||||||
|
if !checkIfLogin() {
|
||||||
|
return []Message{}
|
||||||
|
}
|
||||||
|
|
||||||
var messages []Message
|
var messages []Message
|
||||||
|
|
||||||
err := edgeClient.Query(edgeCtx, `
|
err := edgeClient.Query(edgeCtx, `
|
||||||
@ -195,9 +214,9 @@ func getAllMessages() []Message {
|
|||||||
role,
|
role,
|
||||||
content,
|
content,
|
||||||
date
|
date
|
||||||
} FILTER .conversation.name = 'Default' AND .conversation.user.id = <uuid>$0
|
} FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser
|
||||||
ORDER BY .date ASC
|
ORDER BY .date ASC
|
||||||
`, &messages, CurrentUser.ID)
|
`, &messages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error in edgedb.Query: in getAllMessages")
|
fmt.Println("Error in edgedb.Query: in getAllMessages")
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
|
using extension auth;
|
||||||
|
|
||||||
module default {
|
module default {
|
||||||
|
global current_user_id: uuid;
|
||||||
|
global currentUser := (
|
||||||
|
select User
|
||||||
|
filter .id = global current_user_id
|
||||||
|
);
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
required email: str;
|
|
||||||
required name: str;
|
|
||||||
required setting: Setting;
|
required setting: Setting;
|
||||||
|
required identity: ext::auth::Identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Key {
|
type Key {
|
||||||
|
6
dbschema/migrations/00013-m1frbbh.edgeql
Normal file
6
dbschema/migrations/00013-m1frbbh.edgeql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
CREATE MIGRATION m1frbbhs2tdrqsle67rgzwazozf3qp4xylmnepeacjodtdtcpmqzgq
|
||||||
|
ONTO m16dflw7c2tzuugatxe7g7ngx6ddn6eczk7a7a6oo5zlixscu565ta
|
||||||
|
{
|
||||||
|
CREATE EXTENSION pgcrypto VERSION '1.3';
|
||||||
|
CREATE EXTENSION auth VERSION '1.0';
|
||||||
|
};
|
14
dbschema/migrations/00014-m1ipsij.edgeql
Normal file
14
dbschema/migrations/00014-m1ipsij.edgeql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
CREATE MIGRATION m1ipsijwf3e65w6mvm2622aekxdgvd5hpdlej6mrn2twfbsh47s4mq
|
||||||
|
ONTO m1frbbhs2tdrqsle67rgzwazozf3qp4xylmnepeacjodtdtcpmqzgq
|
||||||
|
{
|
||||||
|
ALTER TYPE default::User {
|
||||||
|
CREATE REQUIRED LINK identity: ext::auth::Identity {
|
||||||
|
SET REQUIRED USING (<ext::auth::Identity>{});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
CREATE GLOBAL default::currentUser := (SELECT
|
||||||
|
default::User
|
||||||
|
FILTER
|
||||||
|
(.identity ?= GLOBAL ext::auth::ClientTokenIdentity)
|
||||||
|
);
|
||||||
|
};
|
8
dbschema/migrations/00015-m1pknui.edgeql
Normal file
8
dbschema/migrations/00015-m1pknui.edgeql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE MIGRATION m1pknuisxcvpidw4gpdqip6qkiohucqgzmv3rcbli3dyvk2hlbmvaa
|
||||||
|
ONTO m1ipsijwf3e65w6mvm2622aekxdgvd5hpdlej6mrn2twfbsh47s4mq
|
||||||
|
{
|
||||||
|
ALTER TYPE default::User {
|
||||||
|
DROP PROPERTY email;
|
||||||
|
DROP PROPERTY name;
|
||||||
|
};
|
||||||
|
};
|
10
dbschema/migrations/00016-m1t2tdd.edgeql
Normal file
10
dbschema/migrations/00016-m1t2tdd.edgeql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
CREATE MIGRATION m1t2tddtq2grfxf4ldn6aj4yotvwiqclsrks6chobvs33d5mcd62mq
|
||||||
|
ONTO m1pknuisxcvpidw4gpdqip6qkiohucqgzmv3rcbli3dyvk2hlbmvaa
|
||||||
|
{
|
||||||
|
CREATE GLOBAL default::current_user_id -> std::uuid;
|
||||||
|
ALTER GLOBAL default::currentUser USING (SELECT
|
||||||
|
default::User
|
||||||
|
FILTER
|
||||||
|
(.id = GLOBAL default::current_user_id)
|
||||||
|
);
|
||||||
|
};
|
128
login.go
Normal file
128
login.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const EDGEDB_AUTH_BASE_URL = "http://127.0.0.1:10700/db/main/ext/auth"
|
||||||
|
|
||||||
|
func generatePKCE() (string, string) {
|
||||||
|
verifier := make([]byte, 32)
|
||||||
|
_, err := rand.Read(verifier)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
challenge := sha256.Sum256(verifier)
|
||||||
|
return base64.RawURLEncoding.EncodeToString(verifier), base64.RawURLEncoding.EncodeToString(challenge[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUiSignIn(c *fiber.Ctx) error {
|
||||||
|
verifier, challenge := generatePKCE()
|
||||||
|
|
||||||
|
fmt.Println("handleUiSignIn verifier: " + verifier)
|
||||||
|
fmt.Println("handleUiSignIn challenge: " + challenge)
|
||||||
|
|
||||||
|
redirectURL := fmt.Sprintf("%s/ui/signin?challenge=%s", EDGEDB_AUTH_BASE_URL, challenge)
|
||||||
|
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "jade-edgedb-pkce-verifier",
|
||||||
|
Value: verifier,
|
||||||
|
HTTPOnly: true,
|
||||||
|
Path: "/",
|
||||||
|
Secure: true,
|
||||||
|
SameSite: "Strict",
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.Redirect(redirectURL, fiber.StatusMovedPermanently)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUiSignUp(c *fiber.Ctx) error {
|
||||||
|
verifier, challenge := generatePKCE()
|
||||||
|
|
||||||
|
fmt.Println("handleUiSignUp verifier: " + verifier)
|
||||||
|
fmt.Println("handleUiSignUp challenge: " + challenge)
|
||||||
|
|
||||||
|
redirectURL := fmt.Sprintf("%s/ui/signup?challenge=%s", EDGEDB_AUTH_BASE_URL, challenge)
|
||||||
|
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "jade-edgedb-pkce-verifier",
|
||||||
|
Value: verifier,
|
||||||
|
HTTPOnly: true,
|
||||||
|
Path: "/",
|
||||||
|
Secure: true,
|
||||||
|
SameSite: "Strict",
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.Redirect(redirectURL, fiber.StatusMovedPermanently)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCallbackSignup(c *fiber.Ctx) error {
|
||||||
|
code := c.Query("code")
|
||||||
|
fmt.Println("Callback signup code: " + code)
|
||||||
|
|
||||||
|
verifier := c.Cookies("jade-edgedb-pkce-verifier")
|
||||||
|
if verifier == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString("Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendString("OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCallback(c *fiber.Ctx) error {
|
||||||
|
code := c.Query("code")
|
||||||
|
if code == "" {
|
||||||
|
error := c.Query("error")
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("OAuth callback is missing 'code'. OAuth provider responded with error: %s", error))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier := c.Cookies("jade-edgedb-pkce-verifier")
|
||||||
|
if verifier == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString("Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("handleCallback code: " + code)
|
||||||
|
fmt.Println("handleCallback verifier: " + verifier)
|
||||||
|
|
||||||
|
codeExchangeURL := fmt.Sprintf("%s/token?code=%s&verifier=%s", EDGEDB_AUTH_BASE_URL, code, verifier)
|
||||||
|
resp, err := http.Get(codeExchangeURL)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error from the auth server: %s", err.Error()))
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != fiber.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error from the auth server: %s", string(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenResponse struct {
|
||||||
|
AuthToken string `json:"auth_token"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&tokenResponse)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error decoding auth server response: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "edgedb-auth-token",
|
||||||
|
Value: tokenResponse.AuthToken,
|
||||||
|
HTTPOnly: true,
|
||||||
|
Path: "/",
|
||||||
|
Secure: true,
|
||||||
|
SameSite: "Strict",
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("Login successful")
|
||||||
|
fmt.Println("edgedb-auth-token: " + tokenResponse.AuthToken)
|
||||||
|
|
||||||
|
return c.SendStatus(fiber.StatusNoContent)
|
||||||
|
}
|
7
main.go
7
main.go
@ -12,13 +12,11 @@ import (
|
|||||||
var userTmpl *pongo2.Template
|
var userTmpl *pongo2.Template
|
||||||
var botTmpl *pongo2.Template
|
var botTmpl *pongo2.Template
|
||||||
var modelsPopoverTmpl *pongo2.Template
|
var modelsPopoverTmpl *pongo2.Template
|
||||||
var conversationsPopoverTmpl *pongo2.Template
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
botTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-bot.html"))
|
botTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-bot.html"))
|
||||||
userTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-user.html"))
|
userTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-user.html"))
|
||||||
modelsPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/modelsPopover.html"))
|
modelsPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/modelsPopover.html"))
|
||||||
conversationsPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/conversationsPopover.html"))
|
|
||||||
|
|
||||||
// Import HTML using django engine/template
|
// Import HTML using django engine/template
|
||||||
engine := django.New("./views", ".html")
|
engine := django.New("./views", ".html")
|
||||||
@ -45,6 +43,11 @@ func main() {
|
|||||||
app.Get("/generateMultipleMessages", GenerateMultipleMessages) // Generate multiple messages
|
app.Get("/generateMultipleMessages", GenerateMultipleMessages) // Generate multiple messages
|
||||||
app.Get("/messageContent", GetMessageContentHandler)
|
app.Get("/messageContent", GetMessageContentHandler)
|
||||||
|
|
||||||
|
app.Get("/auth/ui/signin", handleUiSignIn)
|
||||||
|
app.Get("/auth/ui/signup", handleUiSignUp)
|
||||||
|
app.Get("/auth/callback", handleCallback)
|
||||||
|
app.Get("/auth/callbackSignup", handleCallbackSignup)
|
||||||
|
|
||||||
app.Get("test", func(c *fiber.Ctx) error {
|
app.Get("test", func(c *fiber.Ctx) error {
|
||||||
fmt.Println("test")
|
fmt.Println("test")
|
||||||
return c.SendString("")
|
return c.SendString("")
|
||||||
|
BIN
static/jade.png
BIN
static/jade.png
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 23 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 27 KiB |
@ -1,4 +1,5 @@
|
|||||||
<div class="chat-container mt-5">
|
<div class="chat-container mt-5">
|
||||||
|
|
||||||
<hx hx-get="/loadChat" hx-trigger="load" hx-swap="outerHTML"></hx>
|
<hx hx-get="/loadChat" hx-trigger="load" hx-swap="outerHTML"></hx>
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,6 +53,8 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
<div class="chat-container mt-5">
|
|
||||||
<hx hx-get="/loadChat" hx-trigger="load" hx-swap="outerHTML"></hx>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="chat-input-container">
|
|
||||||
<form class="control" hx-post="/requestMultipleMessages" hx-swap="beforeend settle:200ms"
|
|
||||||
hx-target="#chat-messages" id="chat-input-form" class="chat-input"
|
|
||||||
hx-include="[name='message'], [name^='model-check-']">
|
|
||||||
<div class="textarea-wrapper">
|
|
||||||
<textarea class="textarea" placeholder="Type your message here..." name="message"></textarea>
|
|
||||||
<div class="button-group">
|
|
||||||
<hx hx-get="/loadModelSelection" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
|
|
||||||
<div class="dropdown is-hoverable is-up">
|
|
||||||
<div class="dropdown-trigger">
|
|
||||||
<button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu4">
|
|
||||||
<span>Settings</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown-menu" id="dropdown-menu4" role="menu">
|
|
||||||
<div class="dropdown-content">
|
|
||||||
<div class="dropdown-item">
|
|
||||||
<h5>Hello</h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="send-button button is-primary is-small">Send</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -8,7 +8,7 @@
|
|||||||
hx-target="#content-{{ ConversationAreaId }}" onclick="toggleGrayscale(this)">
|
hx-target="#content-{{ ConversationAreaId }}" onclick="toggleGrayscale(this)">
|
||||||
<figure class="image is-48x48" style="flex-shrink: 0;">
|
<figure class="image is-48x48" style="flex-shrink: 0;">
|
||||||
<img src="icons/{{ message.Icon }}.png" alt="User Image" {% if message.Hidden %}
|
<img src="icons/{{ message.Icon }}.png" alt="User Image" {% if message.Hidden %}
|
||||||
style="filter: grayscale(100%);" {% endif %}>
|
style="filter: grayscale(100%);" {% endif %} title="{{ message.Name }}">
|
||||||
</figure>
|
</figure>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<img id="spinner" class="htmx-indicator" src="/puff.svg" />
|
<div class="navbar-item">
|
||||||
|
<div class="buttons">
|
||||||
|
<a class="button is-light is-small" href="/auth/ui/signin">
|
||||||
|
Log in
|
||||||
|
</a>
|
||||||
|
<a class="button is-primary" href="/auth/ui/signup">
|
||||||
|
<strong>Sign up</strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
Loading…
x
Reference in New Issue
Block a user