diff --git a/apps/survey-admin/index.html b/apps/survey-admin/index.html index 23ff550..2b87072 100644 --- a/apps/survey-admin/index.html +++ b/apps/survey-admin/index.html @@ -7,7 +7,6 @@ -
diff --git a/apps/survey-admin/src/app/app.css.ts b/apps/survey-admin/src/app/app.css.ts new file mode 100644 index 0000000..0782da5 --- /dev/null +++ b/apps/survey-admin/src/app/app.css.ts @@ -0,0 +1,41 @@ +import { vars } from '@ssoon-servey/shared-ui'; +import { style } from '@vanilla-extract/css'; + +export const container = style({ + margin: 'auto', + maxWidth: '90vw', + width: '640px', + marginTop: '12px', + marginBottom: '12px', +}); +export const wrapper = style({ + width: '100%', + display: 'flex', + flexDirection: 'column', + gap: '12px', + position: 'relative', +}); + +export const cardWrapper = style({ + padding: '24px', + paddingTop: '22px', +}); + +export const borderTop = style({ + backgroundColor: vars.color.primary500, + borderTopLeftRadius: '8px', + borderTopRightRadius: '8px', + height: '10px', + position: 'absolute', + top: 0, + left: 0, + right: 0, +}); + +export const toolbar = style({ + position: 'absolute', + top: '106px', + right: '-50px', +}); + +export const surveyTitleInput = style({}); diff --git a/apps/survey-admin/src/app/app.tsx b/apps/survey-admin/src/app/app.tsx index 58cafb5..ac7ad88 100644 --- a/apps/survey-admin/src/app/app.tsx +++ b/apps/survey-admin/src/app/app.tsx @@ -1,68 +1,70 @@ -import { Survey, useCreateSurvey } from '../hooks/query/useServey'; +import { borderTop, wrapper, container, cardWrapper, toolbar } from './app.css'; +import { Card } from '@ssoon-servey/shared-ui'; +import ToolBar from './components/ToolBar'; +import SurveyItem from './components/SurveyItem'; +import useSurveyViewModel from './hooks/viewmodel/useSurveyViewModel'; -const payload: Survey = { - title: '설문입니다.', - sections: [ - { - items: [ - { - title: 'radio input 1번째', - type: 'radio', - required: true, - options: [ - { text: 'radio option1 1번째' }, - { text: 'radio option2 1번째' }, - { text: 'radio option3 1번째' }, - ], - }, - { - title: 'checkbox input 1번째', - type: 'radio', - required: true, - options: [ - { text: 'checkbox option1 1번째' }, - { text: 'checkbox option2 1번째' }, - { text: 'checkbox option3 1번째' }, - ], - }, - ], - }, - { - items: [ - { - title: 'select 1번째', - type: 'select', - required: true, - options: [ - { - text: 'select option1 1번째', - }, - { - text: 'select option2 1번째', - }, - { - text: 'select option3 1번째', - }, - ], - }, - { title: 'select 1번째', type: 'textarea', required: true }, - ], - }, - ], -}; +export default function App() { + const { + survey, + handleSurveyInput, + surveySections, + handleActiveItem, + handleAddOption, + handleChangeOptionText, + handleChangeItemTitle, + handleAddItems, + handleAddSections, + onSubmit, + } = useSurveyViewModel(); -export function App() { - const mutate = useCreateSurvey(); - - const onMutateSurvey = () => { - mutate(payload); - }; return ( -
-

설문 만들기

- +
+
+ +
+
+
+ +
+
+ +
+
+ + {surveySections.map((section, sectionIndex) => ( +
+
{`section ${sectionIndex + 1}`}
+ {section.items.map((item, itemIndex) => ( + handleActiveItem(sectionIndex, itemIndex)} + onAddOptions={handleAddOption} + onChangeOptionText={handleChangeOptionText} + onChangeItemTitle={handleChangeItemTitle} + /> + ))} +
+ ))} +
+ +
+ +
); } - -export default App; diff --git a/apps/survey-admin/src/app/components/SurveyItem/SurveyItem.css.ts b/apps/survey-admin/src/app/components/SurveyItem/SurveyItem.css.ts new file mode 100644 index 0000000..2aa77e0 --- /dev/null +++ b/apps/survey-admin/src/app/components/SurveyItem/SurveyItem.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css'; + +export const cardWrapper = style({ + padding: '24px', + paddingTop: '22px', +}); diff --git a/apps/survey-admin/src/app/components/SurveyItem/SurveyItem.tsx b/apps/survey-admin/src/app/components/SurveyItem/SurveyItem.tsx new file mode 100644 index 0000000..bfd9ff5 --- /dev/null +++ b/apps/survey-admin/src/app/components/SurveyItem/SurveyItem.tsx @@ -0,0 +1,75 @@ +import { Card, Radio } from '@ssoon-servey/shared-ui'; +import { cardWrapper } from './SurveyItem.css'; +import { type SurveyItem } from '../../hooks/query/useServey'; + +interface SurveyItemProps { + item: SurveyItem; + onActiveItem: () => void; + onAddOptions: () => void; + onChangeOptionText: (value: string, optionIndex: number) => void; + onChangeItemTitle: (value: string) => void; +} + +const SurveyItem = ({ + item, + onActiveItem, + onAddOptions, + onChangeOptionText, + onChangeItemTitle, +}: SurveyItemProps) => { + const handleAddOptions = () => { + onAddOptions(); + }; + const handleItemsTitleChange = (e: React.ChangeEvent) => { + const { value } = e.currentTarget; + onChangeItemTitle(value); + }; + const handleOptionTextChange = ( + e: React.ChangeEvent, + optionIndex: number + ) => { + const { value } = e.currentTarget; + onChangeOptionText(value, optionIndex); + }; + return ( + +
+
+ + {/* */} +
+
+ { + <> + {item.options.map((option, i) => ( + + ))} + + + } +
+
+
+ ); +}; + +export default SurveyItem; diff --git a/apps/survey-admin/src/app/components/SurveyItem/index.ts b/apps/survey-admin/src/app/components/SurveyItem/index.ts new file mode 100644 index 0000000..77597a1 --- /dev/null +++ b/apps/survey-admin/src/app/components/SurveyItem/index.ts @@ -0,0 +1 @@ +export { default } from './SurveyItem'; diff --git a/apps/survey-admin/src/app/components/ToolBar/Toolbar.css.ts b/apps/survey-admin/src/app/components/ToolBar/Toolbar.css.ts new file mode 100644 index 0000000..dac9ac5 --- /dev/null +++ b/apps/survey-admin/src/app/components/ToolBar/Toolbar.css.ts @@ -0,0 +1,10 @@ +import { style } from '@vanilla-extract/css'; + +export const cardWrapper = style({ + padding: '8px', + display: 'flex', + flexDirection: 'column', + gap: '4px', + justifyContent: 'center', + alignItems: 'center', +}); diff --git a/apps/survey-admin/src/app/components/ToolBar/Toolbar.tsx b/apps/survey-admin/src/app/components/ToolBar/Toolbar.tsx new file mode 100644 index 0000000..cd55c34 --- /dev/null +++ b/apps/survey-admin/src/app/components/ToolBar/Toolbar.tsx @@ -0,0 +1,37 @@ +import { Card } from '@ssoon-servey/shared-ui'; +import { cardWrapper } from './Toolbar.css'; + +interface Props { + onAddItems: () => void; + onAddSections: () => void; +} + +const ToolBar = ({ onAddItems, onAddSections }: Props) => { + return ( + +
+ + +
+
+ ); +}; + +export default ToolBar; diff --git a/apps/survey-admin/src/app/components/ToolBar/index.ts b/apps/survey-admin/src/app/components/ToolBar/index.ts new file mode 100644 index 0000000..1c8b228 --- /dev/null +++ b/apps/survey-admin/src/app/components/ToolBar/index.ts @@ -0,0 +1 @@ +export { default } from './Toolbar'; diff --git a/apps/survey-admin/src/hooks/query/useServey.ts b/apps/survey-admin/src/app/hooks/query/useServey.ts similarity index 88% rename from apps/survey-admin/src/hooks/query/useServey.ts rename to apps/survey-admin/src/app/hooks/query/useServey.ts index fd1f12e..8cb7ad9 100644 --- a/apps/survey-admin/src/hooks/query/useServey.ts +++ b/apps/survey-admin/src/app/hooks/query/useServey.ts @@ -1,26 +1,26 @@ import { useSupabaseContext } from '@ssoon-servey/supabase'; -interface Option { +export type Option = { text: string; -} +}; -interface Item { +export type SurveyItem = { title: string; type: 'radio' | 'select' | 'checkbox' | 'textarea'; required: boolean; - options?: Option[]; -} + options: Option[]; +}; -interface Section { +export type SurveySection = { title?: string; - items: Item[]; -} + items: SurveyItem[]; +}; -export interface Survey { +export type Survey = { title: string; description?: string; - sections: Section[]; -} + sections: SurveySection[]; +}; export const useCreateSurvey = () => { const { supabase } = useSupabaseContext(); @@ -33,7 +33,10 @@ export const useCreateSurvey = () => { return survey; }; - const createSections = async (serveyId: number, sections: Section[]) => { + const createSections = async ( + serveyId: number, + sections: SurveySection[] + ) => { const { data: survey_section } = await supabase .from('survey_sections') .insert( @@ -48,7 +51,7 @@ export const useCreateSurvey = () => { const createSurveyItems = async ( sectionIds: number[], - sections: Section[] + sections: SurveySection[] ) => { const insertItems: { section_id: number; @@ -80,7 +83,7 @@ export const useCreateSurvey = () => { const createItemOptions = async ( surveyItemIds: number[], - sections: Section[] + sections: SurveySection[] ) => { const insertOptions: { item_id: number; option_text: string }[] = []; diff --git a/apps/survey-admin/src/app/hooks/viewmodel/useSurveyViewModel.ts b/apps/survey-admin/src/app/hooks/viewmodel/useSurveyViewModel.ts new file mode 100644 index 0000000..40fac2f --- /dev/null +++ b/apps/survey-admin/src/app/hooks/viewmodel/useSurveyViewModel.ts @@ -0,0 +1,139 @@ +import React from 'react'; +import { + type SurveySection, + type Survey, + useCreateSurvey, + type SurveyItem as Item, +} from '../query/useServey'; +import { insertItem } from '@ssoon-servey/utils'; +import { useState } from 'react'; +import { produce } from 'immer'; + +const newItem: Item = { + title: '', + type: 'radio', + required: false, + options: [{ text: '옵션1' }], +}; +const newSection: SurveySection = { + title: undefined, + items: [newItem], +}; + +const useSurveyViewModel = () => { + const [survey, setSurvey] = useState>({ + title: '', + description: '', + }); + const [currentActiveItemIndex, setCurrentActiveItemIndex] = useState({ + sectionId: 0, + itemId: 0, + }); + + const [surveySections, setSurveySections] = useState([ + newSection, + ]); + + const mutate = useCreateSurvey(); + + const handleSurveyInput = (e: React.ChangeEvent) => { + const { value, name } = e.currentTarget; + setSurvey((prev) => ({ + ...prev, + [name]: value, + })); + }; + + const handleAddItems = () => { + const { sectionId, itemId } = currentActiveItemIndex; + const nextItemId = itemId + 1; + + setSurveySections((sections) => + produce(sections, (sections) => { + const section = sections[sectionId]; + section.items = insertItem(section.items, nextItemId, newItem); + }) + ); + + setCurrentActiveItemIndex((prev) => ({ + ...prev, + itemId: nextItemId, + })); + }; + + const handleAddOption = () => { + const { sectionId, itemId } = currentActiveItemIndex; + setSurveySections((sections) => + produce(sections, (sections) => { + const section = sections[sectionId]; + const item = section.items[itemId]; + item.options.push({ + text: 'radio option 1번 째', + }); + }) + ); + }; + + const handleAddSections = () => { + const { sectionId } = currentActiveItemIndex; + const nextSectionId = sectionId + 1; + setSurveySections((section) => + insertItem(section, nextSectionId, newSection) + ); + setCurrentActiveItemIndex((prev) => ({ + ...prev, + sectionId: nextSectionId, + })); + }; + + const handleActiveItem = (sectionId: number, itemId: number) => { + setCurrentActiveItemIndex({ sectionId, itemId }); + }; + + const handleChangeItemTitle = (value: string) => { + const { sectionId, itemId } = currentActiveItemIndex; + + setSurveySections((sections) => + produce(sections, (sections) => { + const section = sections[sectionId]; + const item = section.items[itemId]; + item.title = value; + }) + ); + }; + + const handleChangeOptionText = (value: string, optionIndex: number) => { + const { sectionId, itemId } = currentActiveItemIndex; + + setSurveySections((sections) => + produce(sections, (sections) => { + const section = sections[sectionId]; + const item = section.items[itemId]; + const option = item.options[optionIndex]; + option.text = value; + }) + ); + }; + + const onSubmit = () => { + mutate({ + title: survey.title, + description: survey.description, + sections: surveySections, + }); + }; + return { + survey, + handleSurveyInput, + surveySections, + handleActiveItem, + handleAddOption, + handleChangeOptionText, + handleChangeItemTitle, + handleAddItems, + handleAddSections, + onSubmit, + }; +}; + +export default useSurveyViewModel; diff --git a/apps/survey-admin/src/main.tsx b/apps/survey-admin/src/main.tsx index f594e6b..03fb0ab 100644 --- a/apps/survey-admin/src/main.tsx +++ b/apps/survey-admin/src/main.tsx @@ -1,8 +1,8 @@ import { StrictMode } from 'react'; import * as ReactDOM from 'react-dom/client'; import { SupabaseProvider } from '@ssoon-servey/supabase'; - import App from './app/app'; +import '@ssoon-servey/shared-ui/css'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement diff --git a/apps/survey-admin/src/styles.css b/apps/survey-admin/src/styles.css deleted file mode 100644 index 90d4ee0..0000000 --- a/apps/survey-admin/src/styles.css +++ /dev/null @@ -1 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ diff --git a/apps/survey-admin/tsconfig.app.json b/apps/survey-admin/tsconfig.app.json index cd44a1e..d84be45 100644 --- a/apps/survey-admin/tsconfig.app.json +++ b/apps/survey-admin/tsconfig.app.json @@ -19,5 +19,5 @@ "src/**/*.spec.jsx", "src/**/*.test.jsx" ], - "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx", "../../libs/shared/ui/src/global.css.ts"] } diff --git a/apps/survey-admin/vite.config.ts b/apps/survey-admin/vite.config.ts index 80baebe..6fd857e 100644 --- a/apps/survey-admin/vite.config.ts +++ b/apps/survey-admin/vite.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; export default defineConfig({ root: __dirname, @@ -17,7 +18,7 @@ export default defineConfig({ host: 'localhost', }, - plugins: [react(), nxViteTsPaths()], + plugins: [react(), nxViteTsPaths(), vanillaExtractPlugin({})], // Uncomment this if you are using workers. // worker: { diff --git a/libs/shared/ui/src/global.css.ts b/libs/shared/ui/src/global.css.ts new file mode 100644 index 0000000..16aa346 --- /dev/null +++ b/libs/shared/ui/src/global.css.ts @@ -0,0 +1,45 @@ +import { globalStyle, createGlobalTheme } from '@vanilla-extract/css'; + +export const vars = createGlobalTheme(':root', { + color: { + primary100: '#f0ebf8', + primary200: '#e9e7f7', + primary500: '#673ab7', + grayScale00: '#fff', + grayScale100: '#dadce0', + grayScale500: '#adb1ba', + grayScale900: '#202124', + red500: '#d93025', + }, + font: { + body: 'Roboto, RobotoDraft, Helvetica, Arial, sans-serif', + }, +}); + +globalStyle('*, *::before, *::after', { + boxSizing: 'border-box', +}); + +globalStyle('*', { + margin: 0, + padding: 0, + font: 'inherit', +}); + +globalStyle('button', { + cursor: 'pointer', + borderRadius: 0, + border: 'none', + backgroundColor: 'transparent', +}); + +globalStyle('html, body', { + margin: 0, + fontFamily: vars.font.body, + color: vars.color.grayScale900, + accentColor: vars.color.primary500, +}); + +globalStyle('body', { + backgroundColor: vars.color.primary100, +}); diff --git a/libs/shared/ui/src/index.ts b/libs/shared/ui/src/index.ts index cd654c7..bdbcb22 100644 --- a/libs/shared/ui/src/index.ts +++ b/libs/shared/ui/src/index.ts @@ -1 +1,4 @@ -export * from './lib/SharedUi'; +export * from './lib/Card'; +export * from './lib/Radio'; +export * from './lib/Checkbox'; +export * from './global.css'; diff --git a/libs/shared/ui/src/lib/Card/Card.css.ts b/libs/shared/ui/src/lib/Card/Card.css.ts new file mode 100644 index 0000000..239b47a --- /dev/null +++ b/libs/shared/ui/src/lib/Card/Card.css.ts @@ -0,0 +1,15 @@ +import { style } from '@vanilla-extract/css'; +import { vars } from '../../global.css'; + +export const container = style({ + margin: 'auto', + maxWidth: '90vw', + width: '640px', +}); + +export const card = style({ + backgroundColor: vars.color.grayScale00, + border: `1px solid ${vars.color.grayScale100}`, + borderRadius: '8px', + position: 'relative', +}); diff --git a/libs/shared/ui/src/lib/Card/Card.tsx b/libs/shared/ui/src/lib/Card/Card.tsx new file mode 100644 index 0000000..1bfb191 --- /dev/null +++ b/libs/shared/ui/src/lib/Card/Card.tsx @@ -0,0 +1,15 @@ +import { card } from './Card.css'; + +interface CardProps { + children: React.ReactNode; + onClick?: () => void; +} +const Card = ({ children, onClick }: CardProps) => { + return ( +
+ {children} +
+ ); +}; + +export default Card; diff --git a/libs/shared/ui/src/lib/Card/index.ts b/libs/shared/ui/src/lib/Card/index.ts new file mode 100644 index 0000000..06c3388 --- /dev/null +++ b/libs/shared/ui/src/lib/Card/index.ts @@ -0,0 +1 @@ +export { default as Card } from './Card'; diff --git a/libs/shared/ui/src/lib/Checkbox/Checkbox.css.ts b/libs/shared/ui/src/lib/Checkbox/Checkbox.css.ts new file mode 100644 index 0000000..fb260dc --- /dev/null +++ b/libs/shared/ui/src/lib/Checkbox/Checkbox.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css'; + +export const checkbox = style({ + width: '18px', + height: '18px', +}); diff --git a/libs/shared/ui/src/lib/Checkbox/Checkbox.tsx b/libs/shared/ui/src/lib/Checkbox/Checkbox.tsx new file mode 100644 index 0000000..bee7147 --- /dev/null +++ b/libs/shared/ui/src/lib/Checkbox/Checkbox.tsx @@ -0,0 +1,7 @@ +import { checkbox } from './Checkbox.css'; + +const Checkbox = (props: React.InputHTMLAttributes) => { + return ; +}; + +export default Checkbox; diff --git a/libs/shared/ui/src/lib/Checkbox/index.ts b/libs/shared/ui/src/lib/Checkbox/index.ts new file mode 100644 index 0000000..8edbc50 --- /dev/null +++ b/libs/shared/ui/src/lib/Checkbox/index.ts @@ -0,0 +1 @@ +export { default as Checkbox } from './Checkbox'; diff --git a/libs/shared/ui/src/lib/Radio/Radio.css.ts b/libs/shared/ui/src/lib/Radio/Radio.css.ts new file mode 100644 index 0000000..eea956b --- /dev/null +++ b/libs/shared/ui/src/lib/Radio/Radio.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css'; + +export const radio = style({ + width: '18px', + height: '18px', +}); diff --git a/libs/shared/ui/src/lib/Radio/Radio.tsx b/libs/shared/ui/src/lib/Radio/Radio.tsx new file mode 100644 index 0000000..27ffcb5 --- /dev/null +++ b/libs/shared/ui/src/lib/Radio/Radio.tsx @@ -0,0 +1,7 @@ +import { radio } from './Radio.css'; + +const Radio = (props: React.InputHTMLAttributes) => { + return ; +}; + +export default Radio; diff --git a/libs/shared/ui/src/lib/Radio/index.ts b/libs/shared/ui/src/lib/Radio/index.ts new file mode 100644 index 0000000..fe25e2e --- /dev/null +++ b/libs/shared/ui/src/lib/Radio/index.ts @@ -0,0 +1 @@ +export { default as Radio } from './Radio'; diff --git a/libs/shared/ui/src/lib/SharedUi.tsx b/libs/shared/ui/src/lib/SharedUi.tsx deleted file mode 100644 index bd7ef9f..0000000 --- a/libs/shared/ui/src/lib/SharedUi.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import styles from './shared-ui.module.css'; - -/* eslint-disable-next-line */ -export interface SharedUiProps {} - -export function SharedUi(props: SharedUiProps) { - return ( -
-

Welcome to SharedUi!

-
- ); -} - -export default SharedUi; diff --git a/libs/shared/ui/src/lib/shared-ui.module.css b/libs/shared/ui/src/lib/shared-ui.module.css deleted file mode 100644 index 45c2aa4..0000000 --- a/libs/shared/ui/src/lib/shared-ui.module.css +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Replace this with your own classes - * - * e.g. - * .container { - * } -*/ diff --git a/libs/utils/.eslintrc.json b/libs/utils/.eslintrc.json new file mode 100644 index 0000000..adbe7ae --- /dev/null +++ b/libs/utils/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } + } + ] +} diff --git a/libs/utils/README.md b/libs/utils/README.md new file mode 100644 index 0000000..b6583b1 --- /dev/null +++ b/libs/utils/README.md @@ -0,0 +1,7 @@ +# utils + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build utils` to build the library. diff --git a/libs/utils/package.json b/libs/utils/package.json new file mode 100644 index 0000000..893abea --- /dev/null +++ b/libs/utils/package.json @@ -0,0 +1,10 @@ +{ + "name": "@ssoon-servey/utils", + "version": "0.0.1", + "dependencies": { + "tslib": "^2.3.0" + }, + "type": "commonjs", + "main": "./src/index.js", + "typings": "./src/index.d.ts" +} diff --git a/libs/utils/project.json b/libs/utils/project.json new file mode 100644 index 0000000..69d3353 --- /dev/null +++ b/libs/utils/project.json @@ -0,0 +1,19 @@ +{ + "name": "utils", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/utils/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/utils", + "main": "libs/utils/src/index.ts", + "tsConfig": "libs/utils/tsconfig.lib.json", + "assets": ["libs/utils/*.md"] + } + } + }, + "tags": [] +} diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts new file mode 100644 index 0000000..3a4bf34 --- /dev/null +++ b/libs/utils/src/index.ts @@ -0,0 +1 @@ +export * from './lib/utils'; diff --git a/libs/utils/src/lib/utils.ts b/libs/utils/src/lib/utils.ts new file mode 100644 index 0000000..da258af --- /dev/null +++ b/libs/utils/src/lib/utils.ts @@ -0,0 +1,3 @@ +export const insertItem = (array: T[], index: number, newItem: T): T[] => { + return [...array.slice(0, index), newItem, ...array.slice(index)]; +}; diff --git a/libs/utils/tsconfig.json b/libs/utils/tsconfig.json new file mode 100644 index 0000000..db7b566 --- /dev/null +++ b/libs/utils/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libs/utils/tsconfig.lib.json b/libs/utils/tsconfig.lib.json new file mode 100644 index 0000000..faa09cc --- /dev/null +++ b/libs/utils/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/nx.json b/nx.json index 36d4ada..85960bb 100644 --- a/nx.json +++ b/nx.json @@ -53,6 +53,11 @@ "cache": true, "dependsOn": ["^build"], "inputs": ["production", "^production"] + }, + "@nx/js:tsc": { + "cache": true, + "dependsOn": ["^build"], + "inputs": ["production", "^production"] } } } diff --git a/package.json b/package.json index 37abdf0..b892912 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "private": true, "dependencies": { "@supabase/supabase-js": "^2.39.7", + "@vanilla-extract/css": "^1.14.1", + "immer": "^10.0.3", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.11.2" @@ -33,6 +35,7 @@ "@types/react-dom": "18.2.14", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", + "@vanilla-extract/vite-plugin": "^4.0.4", "@vitejs/plugin-react": "^4.2.0", "@vitest/coverage-v8": "^1.0.4", "@vitest/ui": "^1.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43a1f6a..891031d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ dependencies: '@supabase/supabase-js': specifier: ^2.39.7 version: 2.39.7 + '@vanilla-extract/css': + specifier: ^1.14.1 + version: 1.14.1 + immer: + specifier: ^10.0.3 + version: 10.0.3 react: specifier: 18.2.0 version: 18.2.0 @@ -76,6 +82,9 @@ devDependencies: '@typescript-eslint/parser': specifier: ^6.13.2 version: 6.21.0(eslint@8.48.0)(typescript@5.3.3) + '@vanilla-extract/vite-plugin': + specifier: ^4.0.4 + version: 4.0.4(@types/node@18.16.9)(vite@5.0.12) '@vitejs/plugin-react': specifier: ^4.2.0 version: 4.2.1(vite@5.0.12) @@ -1446,7 +1455,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 - dev: true /@babel/template@7.24.0: resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} @@ -1495,6 +1503,9 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true + /@emotion/hash@0.9.1: + resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} + /@esbuild/aix-ppc64@0.19.12: resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} @@ -3294,6 +3305,77 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@vanilla-extract/babel-plugin-debug-ids@1.0.5: + resolution: {integrity: sha512-Rc9A6ylsw7EBErmpgqCMvc/Z/eEZxI5k1xfLQHw7f5HHh3oc5YfzsAsYU/PdmSNjF1dp3sGEViBdDltvwnfVaA==} + dependencies: + '@babel/core': 7.24.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@vanilla-extract/css@1.14.1: + resolution: {integrity: sha512-V4JUuHNjZgl64NGfkDJePqizkNgiSpphODtZEs4cCPuxLAzwOUJYATGpejwimJr1n529kq4DEKWexW22LMBokw==} + dependencies: + '@emotion/hash': 0.9.1 + '@vanilla-extract/private': 1.0.3 + chalk: 4.1.2 + css-what: 6.1.0 + cssesc: 3.0.0 + csstype: 3.1.3 + deep-object-diff: 1.1.9 + deepmerge: 4.3.1 + media-query-parser: 2.0.2 + modern-ahocorasick: 1.0.1 + outdent: 0.8.0 + + /@vanilla-extract/integration@7.1.1(@types/node@18.16.9): + resolution: {integrity: sha512-2JjDKL2HDTazis4oTkUFsQFUyw61k8oym9r0NOLSJC0JB0W25W1ryVZvDx74d3deIyB5AWbCselyzl3gja30kQ==} + dependencies: + '@babel/core': 7.24.0 + '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.24.0) + '@vanilla-extract/babel-plugin-debug-ids': 1.0.5 + '@vanilla-extract/css': 1.14.1 + esbuild: 0.19.12 + eval: 0.1.8 + find-up: 5.0.0 + javascript-stringify: 2.1.0 + lodash: 4.17.21 + mlly: 1.6.1 + outdent: 0.8.0 + vite: 5.0.12(@types/node@18.16.9) + vite-node: 1.3.1(@types/node@18.16.9) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /@vanilla-extract/private@1.0.3: + resolution: {integrity: sha512-17kVyLq3ePTKOkveHxXuIJZtGYs+cSoev7BlP+Lf4916qfDhk/HBjvlYDe8egrea7LNPHKwSZJK/bzZC+Q6AwQ==} + + /@vanilla-extract/vite-plugin@4.0.4(@types/node@18.16.9)(vite@5.0.12): + resolution: {integrity: sha512-cfg4GK274xzwbVFh8YWvQXNnsCMemvMMwej7V93TTBP2O8qzyTgsx5VJuiAPov3oUU8JWGboaTs16Vnoe5bZ9w==} + peerDependencies: + vite: ^4.0.3 || ^5.0.0 + dependencies: + '@vanilla-extract/integration': 7.1.1(@types/node@18.16.9) + vite: 5.0.12(@types/node@18.16.9) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /@vitejs/plugin-react@4.2.1(vite@5.0.12): resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3489,7 +3571,6 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} @@ -3905,7 +3986,6 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} @@ -3975,7 +4055,6 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} @@ -3983,7 +4062,6 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true /colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -4128,7 +4206,11 @@ packages: /css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} - dev: true + + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true /csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} @@ -4146,7 +4228,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dev: true /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -4235,10 +4316,12 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /deep-object-diff@1.1.9: + resolution: {integrity: sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==} + /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - dev: true /defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -4828,6 +4911,14 @@ packages: engines: {node: '>=0.10.0'} dev: true + /eval@0.1.8: + resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} + engines: {node: '>= 0.8'} + dependencies: + '@types/node': 18.16.9 + require-like: 0.1.2 + dev: true + /eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} dev: true @@ -5307,7 +5398,6 @@ packages: /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true /has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -5470,6 +5560,10 @@ packages: engines: {node: '>= 4'} dev: true + /immer@10.0.3: + resolution: {integrity: sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==} + dev: false + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -5768,6 +5862,10 @@ packages: minimatch: 3.1.2 dev: true + /javascript-stringify@2.1.0: + resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} + dev: true + /jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6103,6 +6201,11 @@ packages: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} dev: true + /media-query-parser@2.0.2: + resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} + dependencies: + '@babel/runtime': 7.24.0 + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -6242,6 +6345,9 @@ packages: ufo: 1.4.0 dev: true + /modern-ahocorasick@1.0.1: + resolution: {integrity: sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA==} + /mrmime@2.0.0: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} @@ -6542,6 +6648,9 @@ packages: arch: 2.2.0 dev: true + /outdent@0.8.0: + resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} + /p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -6882,7 +6991,6 @@ packages: /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: true /regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} @@ -6924,6 +7032,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /require-like@0.1.2: + resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + dev: true + /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true @@ -7423,7 +7535,6 @@ packages: engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - dev: true /supports-color@8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} diff --git a/tsconfig.base.json b/tsconfig.base.json index 77b3fee..c10bca3 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,7 +16,9 @@ "baseUrl": ".", "paths": { "@ssoon-servey/shared-ui": ["./libs/shared/ui/src/index.ts"], - "@ssoon-servey/supabase": ["libs/supabase/src/index.ts"] + "@ssoon-servey/shared-ui/css": ["./libs/shared/ui/src/global.css.ts"], + "@ssoon-servey/supabase": ["libs/supabase/src/index.ts"], + "@ssoon-servey/utils": ["libs/utils/src/index.ts"] } }, "exclude": ["node_modules", "tmp"]