Skip to content

Commit

Permalink
Working form submission and basic validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Willdotwhite committed Jan 9, 2024
1 parent 4cedc8d commit b1d4ea3
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 26 deletions.
1 change: 1 addition & 0 deletions api/src/main/kotlin/com/gmtkgamejam/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fun Application.configureRequestHandling() {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,21 @@ data class PostItemUpdateDto (
var availability: Availability?,
var timezoneOffsets: Set<Int>?,
var languages: Set<String>?,
)
)

//author: "dotwo"
//authorId: "427486675409829898"
//availability: "MINIMAL"
//createdAt: "2024-01-09T20:56:06.000Z"
//deletedAt: null
//description: ""
//id: "3555550028524498482"
//languages: ["en"]
//preferredTools: []
//reportCount: 0
//size: 1
//skillsPossessed: ["ART_2D", "CODE"]
//skillsSought: ["ART_3D"]
//timezoneOffsets: [1]
//unableToContactCount: 0
//updatedAt: "2024-01-09T20:56:06.000Z"
2 changes: 1 addition & 1 deletion api/src/main/kotlin/com/gmtkgamejam/routing/PostRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ fun Application.configurePostRouting() {
it.preferredTools = data.preferredTools ?: it.preferredTools
it.languages = data.languages ?: it.languages
it.availability = data.availability ?: it.availability
it.updatedAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
it.updatedAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
it.timezoneOffsets =
(data.timezoneOffsets ?: it.timezoneOffsets).filter { tz -> tz >= -12 && tz <= 12 }
.toSet()
Expand Down
26 changes: 24 additions & 2 deletions api/src/main/kotlin/com/gmtkgamejam/routing/UserInfoRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@ import io.ktor.server.routing.*
import org.koin.ktor.ext.inject
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.time.LocalDateTime
import java.util.*

typealias UserId = String

fun Application.configureUserInfoRouting() {

val logger: Logger = LoggerFactory.getLogger(javaClass)

val bot: DiscordBot by inject()
val service = AuthService()

val shortLiveCache: MutableMap<UserId, Pair<LocalDateTime, UserInfo>> = mutableMapOf()

routing {
authenticate("auth-jwt") { // These routes go through the authentication middleware defined in Auth.kt
get("/hello") {
Expand All @@ -36,11 +41,27 @@ fun Application.configureUserInfoRouting() {
}

get("/userinfo") {
val currentTime = LocalDateTime.now()
val principal = call.principal<JWTPrincipal>()
val id = principal?.payload?.getClaim("id")?.asString()

service.getTokenSet(id!!)?.let {
val tokenSet = it

// Very short TTL cache to avoid unnecessary traffic for quick turnaround behaviour
// We don't expect the cache to expire during regular traffic
if (shortLiveCache.containsKey(tokenSet.discordId)) {
val (cacheSetTime, userInfo) = shortLiveCache[tokenSet.discordId]!!
shortLiveCache.remove(tokenSet.discordId)

// If the cache set was less than 5 minutes ago, don't hit discord again
if (currentTime < cacheSetTime.plusMinutes(5L)) {
// Refresh cache entry
shortLiveCache[userInfo.id] = Pair(LocalDateTime.now(), userInfo)
return@get call.respond(userInfo)
}
}

var accessToken = tokenSet.accessToken

// If access token has expired, try a dirty inline refresh
Expand All @@ -65,8 +86,9 @@ fun Application.configureUserInfoRouting() {
val hasPermissions = bot.doesUserHaveValidPermissions(user.id)
val isUserInGuild = bot.isUserInGuild(user.id)

val userinfo = UserInfo(user, displayName, isUserInGuild, hasPermissions)
return@get call.respond(userinfo)
val userInfo = UserInfo(user, displayName, isUserInGuild, hasPermissions)
shortLiveCache[user.id] = Pair(LocalDateTime.now(), userInfo)
return@get call.respond(userInfo)
}

call.respondJSON("Couldn't load token set from DB", status = HttpStatusCode.NotFound)
Expand Down
6 changes: 1 addition & 5 deletions ui2/src/api/myPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ export function useMyPostMutation(
);
let result;

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
variables.timezoneOffsets = variables.timezoneOffsets.map(str => timezoneOffsetToInt(str))

if (existing) {
result = await apiRequest<PostApiResult>("/posts/mine", {
method: "PUT",
Expand Down Expand Up @@ -100,7 +96,7 @@ export function useMyPostMutation(
}

export interface DeleteMyPostMutationVariables {
id: string;
postId: string;
}
export function useDeleteMyPostMutation(
opts?: UseMutationOptions<Post, Error, DeleteMyPostMutationVariables>
Expand Down
2 changes: 1 addition & 1 deletion ui2/src/common/models/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface Post {
skillsSought: string[];
preferredTools: string[];
availability: string;
timezoneOffsets: number[];
timezoneOffsets: string[];
languages: string[];
isFavourite: boolean;
reportCount: number;
Expand Down
29 changes: 21 additions & 8 deletions ui2/src/pages/mypost/MyPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,28 @@ import {tools} from "../../common/models/engines.tsx";
import {timezones} from "../../common/models/timezones.ts";
import {Post} from "../../common/models/post.ts";

export const MyPost: React.FC<{params: FormikProps<Post>}> = ({params}) => {
export const MyPost: React.FC<{
params: FormikProps<Post>,
author: string,
authorId: string,
hasPost: boolean
}> = ({
params,
author,
authorId,
hasPost
}) => {

const {values, submitForm} = params;

return (
<Form>
<FieldDescription description={values.description} />

{/* This is jank, improve this later with a look up on submit */}
<Field name="author" value={author} type="hidden" />
<Field name="authorId" value={authorId} type="hidden" />

<div className="c-form-block bg-black">
<FieldSkillsPossessed />
<FieldSkillsSought />
Expand All @@ -32,14 +46,13 @@ export const MyPost: React.FC<{params: FormikProps<Post>}> = ({params}) => {

<Button
className="mt-4 bg-blue-700 rounded-xl w-full sm:w-full md:w-auto md:float-right"
type="submit"
type="button"
variant="primary"
disabled={false}
style={{color: "white"}}
onClick={submitForm}
>
Create Post
{/*{`${myPostQuery?.data ? "Update" : "Create"} Post`}*/}
{`${hasPost ? "Update" : "Create"} Post`}
</Button>
</Form>
)
Expand Down Expand Up @@ -114,9 +127,9 @@ const FieldLanguages: React.FC = () => {
const FieldTools: React.FC = () => {
return (
<div>
<label htmlFor="tools">What tools do you want to work with?</label>
<label htmlFor="preferredTools">What tools do you want to work with?</label>
<Field
name="tools"
name="preferredTools"
className="form-block__field"
options={tools}
component={CustomSelect}
Expand All @@ -130,9 +143,9 @@ const FieldTools: React.FC = () => {
const FieldTimezones: React.FC = () => {
return (
<div>
<label htmlFor="timezones">What timezone are you based in?</label>
<label htmlFor="timezoneOffsets">What timezone are you based in?</label>
<Field
name="timezones"
name="timezoneOffsets"
className="form-block__field"
options={timezones}
component={CustomSelect}
Expand Down
46 changes: 38 additions & 8 deletions ui2/src/pages/mypost/MyPostWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,43 @@ import {toast} from "react-hot-toast";
import {useDeleteMyPostMutation, useMyPostMutation, useMyPostQuery} from "../../api/myPost.ts";
import {Button} from "../../common/components/Button.tsx";
import {useEnsureLoggedIn} from "../../api/ensureLoggedIn.ts";
import {useUserInfo} from "../../api/userInfo.ts";

const defaultFormValues: Post = {
description: "",
size: 1,
skillsPossessed: [],
skillsSought: [],
languages: ["en"],
preferredTools: [],
availability: "MINIMAL", //allAvailabilities[0],
timezoneOffsets: ["1"] // ["UTC+0"] as TimezoneOffset[],
}

export const MyPostWrapper: React.FC = () => {

useEnsureLoggedIn();
const userInfo = useUserInfo();
const myPostQuery = useMyPostQuery();
const post = myPostQuery?.data as Post;

const initialValues: Post = {
description: "",
languages: ["en"]
const initialValues: Post = post ? {...post, timezoneOffsets: post?.timezoneOffsets.map(i => i.toString())} : defaultFormValues

const onValidateForm = (values: Post) => {
const errors = []
// @ts-ignore
if (!values.description) errors.push("A description is required")

return errors
}

const onSubmitForm = (values: any) => {
const errors = onValidateForm(values)
if (errors.length > 0) {
errors.map(error => toast.error(error))
return
}

save(values)
console.log(values)
}
Expand All @@ -33,9 +57,15 @@ export const MyPostWrapper: React.FC = () => {
setTimeout(() => window.location.reload(), 200);
}

const { mutate: save, isLoading: isSaving } = useMyPostMutation({onSuccess: onSubmitSuccess});
const { mutate: save } = useMyPostMutation({onSuccess: onSubmitSuccess});
const deletePostMutation = useDeleteMyPostMutation({onSuccess: onDeleteSuccess});

/** Ensure user is logged in to view the page; give them enough information to see what's happening */
if (userInfo?.isLoading || !userInfo.data) {return (<main><div className="c-form bg-black"><h1 className="text-3xl my-4">Please wait...</h1></div></main>)}

/** Ensure we have active form data before rendering form */
if (myPostQuery?.isLoading) {return (<></>)}

return (
<main>
<div className="c-form bg-black">
Expand All @@ -46,25 +76,25 @@ export const MyPostWrapper: React.FC = () => {
{(params: FormikProps<Post>) => (
<>
<h1 className="text-3xl my-4">Create New Post</h1>
<MyPost params={params} />
<MyPost params={params} author={userInfo.data!.username} authorId={userInfo.data!.userId} hasPost={Boolean(post)} />
</>
)}
</Formik>
{post && <DeletePostButton postId={post.id} onClick={deletePostMutation} />}
{post && <DeletePostButton postId={post.id} onClickHandler={() => deletePostMutation.mutate({ postId: post.id })} />}
</div>
</main>
)
}

const DeletePostButton: React.FC<{postId: string, onClick: any}> = ({postId, onClick}) => {
const DeletePostButton: React.FC<{postId: string, onClickHandler: any}> = ({onClickHandler}) => {
return (
<Button
className="mt-4 bg-red-700 rounded-xl w-full sm:w-full md:w-auto md:left"
type="submit"
variant="default"
disabled={false}
style={{color: "white"}}
onClick={onClick.mutate({ id: postId })}
onClick={onClickHandler}
>
Delete Post
</Button>
Expand Down

0 comments on commit b1d4ea3

Please sign in to comment.