Skip to content

Commit c601add

Browse files
authored
update(Frontend v2): Input validation + Toasts (#105)
1 parent 0856793 commit c601add

18 files changed

Lines changed: 279 additions & 40 deletions

frontend/package-lock.json

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"@vue-flow/core": "^1.45.0",
1818
"cytoscape": "^3.32.1",
1919
"pinia": "^3.0.3",
20-
"vue": "^3.5.17"
20+
"vue": "^3.5.17",
21+
"vue-toastification": "^2.0.0-rc.5"
2122
},
2223
"devDependencies": {
2324
"@tsconfig/node22": "^22.0.2",

frontend/src/App.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ import DatabaseStep from "./components/steps/DatabaseStep.vue"
3737
import SchemaStep from "./components/steps/SchemaStep.vue"
3838
import GlobalModal from "@/components/modal/GlobalModal.vue"
3939
40-
const steps: Array<any> = [SchemaStep, ProjectNameStep, DatabaseStep, SchemaStep]
40+
import type { Component } from "vue"
41+
42+
const steps: Component[] = [ProjectNameStep, DatabaseStep, SchemaStep]
4143
</script>
4244

4345
<style>

frontend/src/assets/main.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,25 @@ body {
88
--color-secondary: #dcebfe;
99
--color-success: #7fbc8c;
1010
--color-danger: #ff6b6b;
11+
--color-warning: #f5c26b;
1112
--color-background: #f4f4f0;
1213
}
14+
15+
.Vue-Toastification__toast {
16+
border: 2px solid black;
17+
border-radius: 0px !important;
18+
color: black !important;
19+
box-shadow: 2px 2px 0px !important;
20+
}
21+
22+
.Vue-Toastification__toast--success.container-class {
23+
background-color: var(--color-success);
24+
}
25+
26+
.Vue-Toastification__toast--error.container-class {
27+
background-color: var(--color-danger);
28+
}
29+
30+
.Vue-Toastification__toast--warning.container-class {
31+
background-color: var(--color-warning);
32+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<template>
2+
<div class="validated-input">
3+
<label v-if="label" class="field-label">{{ label }}</label>
4+
5+
<input
6+
v-bind="$attrs"
7+
:value="modelValue"
8+
@input="onInput"
9+
:class="{ invalid: error }"
10+
class="field-input"
11+
/>
12+
13+
<p v-if="error" class="error-msg">{{ error }}</p>
14+
</div>
15+
</template>
16+
17+
<script setup lang="ts">
18+
import { ref, watch } from "vue"
19+
20+
interface Props {
21+
modelValue: string
22+
label?: string
23+
validate?: ((value: string) => boolean) | ((value: string) => string | null)
24+
errorMessage?: string // used if validate returns boolean
25+
}
26+
27+
const props = defineProps<Props>()
28+
const emit = defineEmits(["update:modelValue"])
29+
30+
const error = ref<string | null>(null)
31+
32+
const runValidation = (value: string) => {
33+
if (!props.validate) {
34+
error.value = null
35+
return
36+
}
37+
38+
const result = props.validate(value)
39+
40+
if (typeof result === "boolean") {
41+
error.value = result ? null : (props.errorMessage ?? "Invalid value")
42+
} else {
43+
error.value = result // already string|null from custom function
44+
}
45+
}
46+
47+
const onInput = (e: Event) => {
48+
const value = (e.target as HTMLInputElement).value
49+
emit("update:modelValue", value)
50+
runValidation(value)
51+
}
52+
53+
watch(
54+
() => props.modelValue,
55+
(newVal) => runValidation(newVal),
56+
{ immediate: true },
57+
)
58+
</script>
59+
60+
<style scoped>
61+
.field-label {
62+
font-weight: bold;
63+
margin-bottom: 5px;
64+
display: block;
65+
}
66+
67+
.field-input {
68+
border: 2px solid black;
69+
border-radius: 6px;
70+
padding: 0.6rem;
71+
background-color: white;
72+
width: 100%;
73+
}
74+
75+
.field-input.invalid {
76+
border-color: red;
77+
}
78+
79+
.error-msg {
80+
color: red;
81+
font-size: 0.85rem;
82+
margin-top: 2px;
83+
}
84+
85+
.validated-input {
86+
display: flex;
87+
flex-direction: column;
88+
gap: 0.15rem;
89+
}
90+
</style>

frontend/src/components/modal/AddEnumValueModal.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
<div class="input-container">
44
<div class="input-group">
55
<label class="field-label">Name</label>
6-
<input class="field-input" v-model="name" type="text" />
6+
<input class="field-input" v-model="name" type="text" maxlength="100" />
77
</div>
88

99
<div class="input-group">
1010
<label class="field-label">Value</label>
11-
<input class="field-input" v-model="value" type="text" />
11+
<input class="field-input" v-model="value" type="text" maxlength="100" />
1212
</div>
1313
</div>
1414

@@ -22,6 +22,8 @@
2222
import { ref } from "vue"
2323
import { useProjectStore } from "@/stores/useProjectStore"
2424
import { useModalStore } from "@/stores/useModalStore"
25+
import { isValidEnumValueName, warningMessages } from "@/utils/validation"
26+
import { showDangerToast } from "@/utils/toast"
2527
2628
const props = defineProps<{
2729
enumName: string
@@ -34,6 +36,10 @@ const name = ref("")
3436
const value = ref("auto()")
3537
3638
const saveEnumValue = () => {
39+
if (!isValidEnumValueName(name.value)) {
40+
showDangerToast(warningMessages.enumValueName)
41+
return
42+
}
3743
projectStore.addEnumValue(props.enumName, {
3844
name: name.value,
3945
value: value.value,

frontend/src/components/modal/AddFieldModal.vue

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div class="input-container">
44
<div class="input-group">
55
<label class="field-label">Field name</label>
6-
<input class="field-input" v-model="fieldName" type="text" />
6+
<input class="field-input" v-model="fieldName" type="text" maxlength="100" />
77
</div>
88

99
<div class="input-group">
@@ -44,10 +44,11 @@
4444
</option>
4545
</select>
4646
<input
47+
v-else
4748
class="field-input"
4849
v-model="defaultValue"
4950
type="text"
50-
v-else
51+
maxlength="100"
5152
:disabled="isPrimaryKey || createdAtTimestamp || updatedAtTimestamp"
5253
/>
5354
</div>
@@ -77,6 +78,8 @@ import { ref, watch } from "vue"
7778
import { useProjectStore } from "@/stores/useProjectStore"
7879
import { useModalStore } from "@/stores/useModalStore"
7980
import type { EnumT, FieldType } from "@/types/types"
81+
import { isValidFieldName, warningMessages } from "@/utils/validation"
82+
import { showDangerToast } from "@/utils/toast"
8083
8184
const props = defineProps<{
8285
id: string
@@ -143,6 +146,10 @@ watch(type, (newVal) => {
143146
144147
const saveField = () => {
145148
if (!fieldName.value || !type.value) return
149+
if (!isValidFieldName(fieldName.value)) {
150+
showDangerToast(warningMessages.fieldName)
151+
return
152+
}
146153
projectStore.addField(props.id, {
147154
name: fieldName.value,
148155
type: type.value as FieldType,

frontend/src/components/modal/AddRelationModal.vue

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div class="input-container">
44
<div class="input-group">
55
<label class="relation-label">Field name</label>
6-
<input class="relation-input" v-model="fieldName" type="text" />
6+
<input class="relation-input" v-model="fieldName" type="text" maxlength="100" />
77
</div>
88

99
<div class="input-group">
@@ -27,7 +27,7 @@
2727

2828
<div class="input-group">
2929
<label class="relation-label">Back populates</label>
30-
<input class="relation-input" v-model="backPopulates" type="text" />
30+
<input class="relation-input" v-model="backPopulates" type="text" maxlength="100" />
3131
</div>
3232
</div>
3333

@@ -47,6 +47,9 @@
4747
import { ref, computed } from "vue"
4848
import { useProjectStore } from "@/stores/useProjectStore"
4949
import { useModalStore } from "@/stores/useModalStore"
50+
import type { OnDeleteType } from "@/types/types"
51+
import { isValidFieldName, warningMessages } from "@/utils/validation"
52+
import { showDangerToast } from "@/utils/toast"
5053
5154
const props = defineProps<{
5255
id: string
@@ -67,11 +70,15 @@ const filteredNodes = computed(() => projectStore.nodes.filter((node) => node.id
6770
6871
const saveSelect = () => {
6972
if (!selectedNodeId.value || !fieldName.value) return
73+
if (!isValidFieldName(fieldName.value)) {
74+
showDangerToast(warningMessages.fieldName)
75+
return
76+
}
7077
projectStore.addRelation(props.id, selectedNodeId.value, {
7178
fieldName: fieldName.value,
7279
targetModel: selectedNodeId.value,
7380
backPopulates: backPopulates.value,
74-
onDelete: onDelete.value,
81+
onDelete: onDelete.value as OnDeleteType,
7582
isNullable: nullable.value,
7683
isUnique: unique.value,
7784
isIndex: indexed.value,

frontend/src/components/modal/EditEnumValueModal.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
<div class="input-container">
44
<div class="input-group">
55
<label class="field-label">Name</label>
6-
<input class="field-input" v-model="name" type="text" />
6+
<input class="field-input" v-model="name" type="text" maxlength="100" />
77
</div>
88

99
<div class="input-group">
1010
<label class="field-label">Value</label>
11-
<input class="field-input" v-model="value" type="text" />
11+
<input class="field-input" v-model="value" type="text" maxlength="100" />
1212
</div>
1313
</div>
1414

@@ -23,6 +23,8 @@ import { ref } from "vue"
2323
import { useProjectStore } from "@/stores/useProjectStore"
2424
import { useModalStore } from "@/stores/useModalStore"
2525
import type { EnumValue } from "@/types/types"
26+
import { isValidEnumValueName, warningMessages } from "@/utils/validation"
27+
import { showDangerToast } from "@/utils/toast"
2628
2729
const props = defineProps<{
2830
enumName: string
@@ -36,6 +38,10 @@ const name = ref(props.enumValue.name)
3638
const value = ref(props.enumValue.value)
3739
3840
const saveEnumValue = () => {
41+
if (!isValidEnumValueName(name.value)) {
42+
showDangerToast(warningMessages.enumValueName)
43+
return
44+
}
3945
projectStore.updateEnumValue(props.enumName, props.enumValue.name, {
4046
name: name.value,
4147
value: value.value,
@@ -101,3 +107,5 @@ const saveEnumValue = () => {
101107
box-shadow: none;
102108
}
103109
</style>
110+
111+
function showDangerToast(modelName: any) { throw new Error("Function not implemented."); }

frontend/src/components/modal/EditFieldModal.vue

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div class="input-container">
44
<div class="input-group">
55
<label class="field-label">Field name</label>
6-
<input class="field-input" v-model="fieldName" type="text" />
6+
<input class="field-input" v-model="fieldName" type="text" maxlength="100" />
77
</div>
88

99
<div class="input-group">
@@ -44,10 +44,11 @@
4444
</option>
4545
</select>
4646
<input
47+
v-else
4748
class="field-input"
4849
v-model="defaultValue"
4950
type="text"
50-
v-else
51+
maxlength="100"
5152
:disabled="isPrimaryKey || createdAtTimestamp || updatedAtTimestamp"
5253
/>
5354
</div>
@@ -78,6 +79,8 @@ import { ref, onMounted, watch } from "vue"
7879
import { useProjectStore } from "@/stores/useProjectStore"
7980
import { useModalStore } from "@/stores/useModalStore"
8081
import type { EnumT, RelationalField } from "@/types/types"
82+
import { isValidFieldName, warningMessages } from "@/utils/validation"
83+
import { showDangerToast } from "@/utils/toast"
8184
8285
const props = defineProps<{
8386
id: string
@@ -153,6 +156,10 @@ watch(type, (newVal) => {
153156
})
154157
155158
const saveField = () => {
159+
if (!isValidFieldName(fieldName.value)) {
160+
showDangerToast(warningMessages.fieldName)
161+
return
162+
}
156163
projectStore.updateField(props.id, props.field.name, {
157164
name: fieldName.value,
158165
type: type.value,

0 commit comments

Comments
 (0)