diff --git a/app/models/design-layer/design-layer.create.server.ts b/app/models/design-layer/design-layer.create.server.ts
new file mode 100644
index 00000000..5af26063
--- /dev/null
+++ b/app/models/design-layer/design-layer.create.server.ts
@@ -0,0 +1,18 @@
+import { type IntentActionArgs } from '#app/definitions/intent-action-args'
+import { NewLayerDesignSchema } from '#app/schema/design-layer'
+import { ValidateLayerParentSubmissionStrategy } from '#app/strategies/validate-submission.strategy'
+import { validateEntitySubmission } from '#app/utils/conform-utils'
+
+export const validateLayerNewDesignSubmission = async ({
+ userId,
+ formData,
+}: IntentActionArgs) => {
+ const strategy = new ValidateLayerParentSubmissionStrategy()
+
+ return await validateEntitySubmission({
+ userId,
+ formData,
+ schema: NewLayerDesignSchema,
+ strategy,
+ })
+}
diff --git a/app/models/design-layer/design-layer.delete.server.ts b/app/models/design-layer/design-layer.delete.server.ts
new file mode 100644
index 00000000..e43a09c6
--- /dev/null
+++ b/app/models/design-layer/design-layer.delete.server.ts
@@ -0,0 +1,18 @@
+import { type IntentActionArgs } from '#app/definitions/intent-action-args'
+import { DeleteLayerDesignSchema } from '#app/schema/design-layer'
+import { ValidateLayerParentSubmissionStrategy } from '#app/strategies/validate-submission.strategy'
+import { validateEntitySubmission } from '#app/utils/conform-utils'
+
+export const validateLayerDeleteDesignSubmission = async ({
+ userId,
+ formData,
+}: IntentActionArgs) => {
+ const strategy = new ValidateLayerParentSubmissionStrategy()
+
+ return await validateEntitySubmission({
+ userId,
+ formData,
+ schema: DeleteLayerDesignSchema,
+ strategy,
+ })
+}
diff --git a/app/models/design-layer/design-layer.update.server.ts b/app/models/design-layer/design-layer.update.server.ts
new file mode 100644
index 00000000..7040d4a2
--- /dev/null
+++ b/app/models/design-layer/design-layer.update.server.ts
@@ -0,0 +1,35 @@
+import { type IntentActionArgs } from '#app/definitions/intent-action-args'
+import {
+ ReorderLayerDesignSchema,
+ ToggleVisibleLayerDesignSchema,
+} from '#app/schema/design-layer'
+import { ValidateLayerParentSubmissionStrategy } from '#app/strategies/validate-submission.strategy'
+import { validateEntitySubmission } from '#app/utils/conform-utils'
+
+export const validateLayerToggleVisibeDesignSubmission = async ({
+ userId,
+ formData,
+}: IntentActionArgs) => {
+ const strategy = new ValidateLayerParentSubmissionStrategy()
+
+ return await validateEntitySubmission({
+ userId,
+ formData,
+ schema: ToggleVisibleLayerDesignSchema,
+ strategy,
+ })
+}
+
+export const validateLayerReorderDesignSubmission = async ({
+ userId,
+ formData,
+}: IntentActionArgs) => {
+ const strategy = new ValidateLayerParentSubmissionStrategy()
+
+ return await validateEntitySubmission({
+ userId,
+ formData,
+ schema: ReorderLayerDesignSchema,
+ strategy,
+ })
+}
diff --git a/app/routes/resources+/api.v1+/artboard-version.design.update.visible.tsx b/app/routes/resources+/api.v1+/artboard-version.design.update.visible.tsx
index 436d79b1..9eaec5c5 100644
--- a/app/routes/resources+/api.v1+/artboard-version.design.update.visible.tsx
+++ b/app/routes/resources+/api.v1+/artboard-version.design.update.visible.tsx
@@ -6,7 +6,7 @@ import {
import { redirectBack } from 'remix-utils/redirect-back'
import { validateArtboardVersionToggleVisibeDesignSubmission } from '#app/models/design-artboard-version/design-artboard-version.update.server'
import { validateNoJS } from '#app/schema/form-data'
-import { artboardDesignToggleVisibleService } from '#app/services/artboard/design/toggle-visible.service'
+import { artboardVersionDesignToggleVisibleService } from '#app/services/artboard/version/design/toggle-visible.service'
import { requireUserId } from '#app/utils/auth.server'
// https://www.epicweb.dev/full-stack-components
@@ -29,7 +29,7 @@ export async function action({ request }: DataFunctionArgs) {
})
if (status === 'success') {
- const { success } = await artboardDesignToggleVisibleService({
+ const { success } = await artboardVersionDesignToggleVisibleService({
userId,
...submission.value,
})
diff --git a/app/routes/resources+/api.v1+/layer.design.create.tsx b/app/routes/resources+/api.v1+/layer.design.create.tsx
new file mode 100644
index 00000000..1f6e9767
--- /dev/null
+++ b/app/routes/resources+/api.v1+/layer.design.create.tsx
@@ -0,0 +1,50 @@
+import {
+ type LoaderFunctionArgs,
+ json,
+ type DataFunctionArgs,
+} from '@remix-run/node'
+import { redirectBack } from 'remix-utils/redirect-back'
+import { validateLayerNewDesignSubmission } from '#app/models/design-layer/design-layer.create.server'
+import { validateNoJS } from '#app/schema/form-data'
+import { layerDesignCreateService } from '#app/services/layer/design/create.service'
+import { requireUserId } from '#app/utils/auth.server'
+
+// https://www.epicweb.dev/full-stack-components
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ await requireUserId(request)
+ return json({})
+}
+
+export async function action({ request }: DataFunctionArgs) {
+ const userId = await requireUserId(request)
+ const formData = await request.formData()
+ const noJS = validateNoJS({ formData })
+
+ let createSuccess = false
+ const { status, submission } = await validateLayerNewDesignSubmission({
+ userId,
+ formData,
+ })
+
+ if (status === 'success') {
+ const { success } = await layerDesignCreateService({
+ userId,
+ ...submission.value,
+ })
+ createSuccess = success
+ }
+
+ if (noJS) {
+ throw redirectBack(request, {
+ fallback: '/',
+ })
+ }
+
+ return json(
+ { status, submission },
+ {
+ status: status === 'error' || !createSuccess ? 422 : 201,
+ },
+ )
+}
diff --git a/app/routes/resources+/api.v1+/layer.design.delete.tsx b/app/routes/resources+/api.v1+/layer.design.delete.tsx
new file mode 100644
index 00000000..b948a0d8
--- /dev/null
+++ b/app/routes/resources+/api.v1+/layer.design.delete.tsx
@@ -0,0 +1,50 @@
+import {
+ type LoaderFunctionArgs,
+ json,
+ type DataFunctionArgs,
+} from '@remix-run/node'
+import { redirectBack } from 'remix-utils/redirect-back'
+import { validateLayerDeleteDesignSubmission } from '#app/models/design-layer/design-layer.delete.server'
+import { validateNoJS } from '#app/schema/form-data'
+import { layerDesignDeleteService } from '#app/services/layer/design/delete.service'
+import { requireUserId } from '#app/utils/auth.server'
+
+// https://www.epicweb.dev/full-stack-components
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ await requireUserId(request)
+ return json({})
+}
+
+export async function action({ request }: DataFunctionArgs) {
+ const userId = await requireUserId(request)
+ const formData = await request.formData()
+ const noJS = validateNoJS({ formData })
+
+ let deleteSuccess = false
+ const { status, submission } = await validateLayerDeleteDesignSubmission({
+ userId,
+ formData,
+ })
+
+ if (status === 'success') {
+ const { success } = await layerDesignDeleteService({
+ userId,
+ ...submission.value,
+ })
+ deleteSuccess = success
+ }
+
+ if (noJS) {
+ throw redirectBack(request, {
+ fallback: '/',
+ })
+ }
+
+ return json(
+ { status, submission },
+ {
+ status: status === 'error' || !deleteSuccess ? 404 : 200,
+ },
+ )
+}
diff --git a/app/routes/resources+/api.v1+/layer.design.update.order.tsx b/app/routes/resources+/api.v1+/layer.design.update.order.tsx
new file mode 100644
index 00000000..d5b0f41a
--- /dev/null
+++ b/app/routes/resources+/api.v1+/layer.design.update.order.tsx
@@ -0,0 +1,58 @@
+import {
+ type LoaderFunctionArgs,
+ json,
+ type DataFunctionArgs,
+} from '@remix-run/node'
+import { redirectBack } from 'remix-utils/redirect-back'
+import { validateLayerReorderDesignSubmission } from '#app/models/design-layer/design-layer.update.server'
+import { validateNoJS } from '#app/schema/form-data'
+import { layerDesignMoveDownService } from '#app/services/layer/design/move-down.service'
+import { layerDesignMoveUpService } from '#app/services/layer/design/move-up.service'
+import { requireUserId } from '#app/utils/auth.server'
+
+// https://www.epicweb.dev/full-stack-components
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ await requireUserId(request)
+ return json({})
+}
+
+export async function action({ request }: DataFunctionArgs) {
+ const userId = await requireUserId(request)
+ const formData = await request.formData()
+ const noJS = validateNoJS({ formData })
+
+ let updateSuccess = false
+ const { status, submission } = await validateLayerReorderDesignSubmission({
+ userId,
+ formData,
+ })
+
+ if (status === 'success') {
+ const { direction } = submission.value
+ const { success } =
+ direction === 'up'
+ ? await layerDesignMoveUpService({
+ userId,
+ ...submission.value,
+ })
+ : await layerDesignMoveDownService({
+ userId,
+ ...submission.value,
+ })
+ updateSuccess = success
+ }
+
+ if (noJS) {
+ throw redirectBack(request, {
+ fallback: '/',
+ })
+ }
+
+ return json(
+ { status, submission },
+ {
+ status: status === 'error' || !updateSuccess ? 404 : 200,
+ },
+ )
+}
diff --git a/app/routes/resources+/api.v1+/layer.design.update.visible.tsx b/app/routes/resources+/api.v1+/layer.design.update.visible.tsx
new file mode 100644
index 00000000..4e650ad6
--- /dev/null
+++ b/app/routes/resources+/api.v1+/layer.design.update.visible.tsx
@@ -0,0 +1,51 @@
+import {
+ type LoaderFunctionArgs,
+ json,
+ type DataFunctionArgs,
+} from '@remix-run/node'
+import { redirectBack } from 'remix-utils/redirect-back'
+import { validateLayerToggleVisibeDesignSubmission } from '#app/models/design-layer/design-layer.update.server'
+import { validateNoJS } from '#app/schema/form-data'
+import { layerDesignToggleVisibleService } from '#app/services/layer/design/toggle-visible.service'
+import { requireUserId } from '#app/utils/auth.server'
+
+// https://www.epicweb.dev/full-stack-components
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ await requireUserId(request)
+ return json({})
+}
+
+export async function action({ request }: DataFunctionArgs) {
+ const userId = await requireUserId(request)
+ const formData = await request.formData()
+ const noJS = validateNoJS({ formData })
+
+ let updateSuccess = false
+ const { status, submission } =
+ await validateLayerToggleVisibeDesignSubmission({
+ userId,
+ formData,
+ })
+
+ if (status === 'success') {
+ const { success } = await layerDesignToggleVisibleService({
+ userId,
+ ...submission.value,
+ })
+ updateSuccess = success
+ }
+
+ if (noJS) {
+ throw redirectBack(request, {
+ fallback: '/',
+ })
+ }
+
+ return json(
+ { status, submission },
+ {
+ status: status === 'error' || !updateSuccess ? 404 : 200,
+ },
+ )
+}
diff --git a/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.tsx b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.tsx
index 53900c3f..d827d0cb 100644
--- a/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.tsx
+++ b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.tsx
@@ -1,7 +1,7 @@
import { type IArtboardVersionWithDesignsAndLayers } from '#app/models/artboard-version/artboard-version.server'
import { PanelArtboardVersionBackground } from './sidebars.panel.artboard-version.background'
-import { PanelArtboardVersionDesigns } from './sidebars.panel.artboard-version.designs'
import { PanelArtboardVersionFrame } from './sidebars.panel.artboard-version.frame'
+import { PanelArtboardVersionDesigns } from './sidebars.panel.designs.artboard-version'
export const PanelArtboardVersion = ({
version,
diff --git a/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.designs.tsx b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.designs.artboard-version.tsx
similarity index 100%
rename from app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.designs.tsx
rename to app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.designs.artboard-version.tsx
diff --git a/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.designs.layer.tsx b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.designs.layer.tsx
new file mode 100644
index 00000000..ed03b762
--- /dev/null
+++ b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.designs.layer.tsx
@@ -0,0 +1,20 @@
+import { type ILayerWithDesigns } from '#app/models/layer.server'
+import { DashboardPanelCreateLayerDesignTypeStrategy } from '#app/strategies/component/dashboard-panel/create-entity.strategy'
+import { DashboardPanelLayerDesignActionStrategy } from '#app/strategies/component/dashboard-panel/entity-action/entity-action'
+import { DashboardPanelUpdateLayerDesignTypeOrderStrategy } from '#app/strategies/component/dashboard-panel/update-entity-order.strategy'
+import { PanelDesigns } from './sidebars.panel.designs'
+
+export const PanelLayerDesigns = ({ layer }: { layer: ILayerWithDesigns }) => {
+ const strategyEntityNew = new DashboardPanelCreateLayerDesignTypeStrategy()
+ const strategyReorder = new DashboardPanelUpdateLayerDesignTypeOrderStrategy()
+ const strategyActions = new DashboardPanelLayerDesignActionStrategy()
+
+ return (
+
yo
} -