From 24cd8b1551c08e1d32c14efea030fcbf835ca148 Mon Sep 17 00:00:00 2001 From: Ilya Egorov Date: Tue, 22 Oct 2024 18:02:24 +0300 Subject: [PATCH 1/4] CI --- .github/CODEOWNERS | 2 + .github/workflows/deploy.yml | 130 ++++++++++++++++++++++ .idea/.gitignore | 8 ++ .idea/material_theme_project_new.xml | 12 ++ .idea/vcs.xml | 6 + .idea/web-ide.iml | 9 ++ Dockerfile | 10 ++ helm/app/Chart.yaml | 4 + helm/app/templates/deployment.yaml | 54 +++++++++ helm/app/templates/ghcr-secret.yaml | 10 ++ helm/app/templates/hpa.yaml | 25 +++++ helm/app/templates/ingress.yaml | 43 +++++++ helm/app/templates/ns-resource-quota.yaml | 27 +++++ helm/app/templates/service-monitor.yaml | 19 ++++ helm/app/templates/service.yaml | 17 +++ helm/app/templates/tls-cert.yaml | 15 +++ helm/app/values-production.yaml | 9 ++ helm/app/values-staging.yaml | 9 ++ helm/app/values.yaml | 41 +++++++ 19 files changed, 450 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/deploy.yml create mode 100644 .idea/.gitignore create mode 100644 .idea/material_theme_project_new.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/web-ide.iml create mode 100644 Dockerfile create mode 100644 helm/app/Chart.yaml create mode 100644 helm/app/templates/deployment.yaml create mode 100644 helm/app/templates/ghcr-secret.yaml create mode 100644 helm/app/templates/hpa.yaml create mode 100644 helm/app/templates/ingress.yaml create mode 100644 helm/app/templates/ns-resource-quota.yaml create mode 100644 helm/app/templates/service-monitor.yaml create mode 100644 helm/app/templates/service.yaml create mode 100644 helm/app/templates/tls-cert.yaml create mode 100644 helm/app/values-production.yaml create mode 100644 helm/app/values-staging.yaml create mode 100644 helm/app/values.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4598c5a --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +/helm/ @Mobyman +/.github/ @Mobyman \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..bf68d00 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,130 @@ +name: Deploy to Kubernetes + +on: + push: + branches: + - main + - canary + - staging + +jobs: + build: + runs-on: ubuntu-latest + env: + APP_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || github.ref == 'refs/heads/canary' && 'canary' || github.ref == 'refs/heads/staging' && 'staging' || 'unknown' }} + APP_DOMAIN: ${{ github.ref == 'refs/heads/staging' && vars.APP_DOMAIN_STAGING || vars.APP_DOMAIN }} + + permissions: + packages: write + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ vars.AWS_REGION }} + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set sha-short + run: echo "GITHUB_SHA_SHORT=$(echo $GITHUB_SHA | cut -c 1-7)" >> $GITHUB_ENV + + - id: lower-repo + name: Repository to lowercase + run: | + echo "repository=${GITHUB_REPOSITORY@L}" >> $GITHUB_OUTPUT + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ steps.lower-repo.outputs.repository }} + github-token: ${{ secrets.GITHUB_TOKEN }} + tags: | + type=sha + type=sha,format=long + type=ref,event=branch + + - name: Build and push Docker image ${{ steps.lower-repo.outputs.repository }}:${{ env.APP_ENV }} + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ env.GITHUB_SHA_SHORT }},ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ env.APP_ENV }} + build-args: | + sha=${{ github.sha }} + sha_short=${{ env.GITHUB_SHA_SHORT }} + app_env=${{ vars.APP_ENV }} + + - name: Apply AWS k8s config + run: aws eks update-kubeconfig --name ${{ vars.AWS_CLUSTER }} --region ${{ vars.AWS_REGION }} + + - name: Create namespace + run: | + kubectl create ns ${{ vars.APP_NAME }}-${{ env.APP_ENV }} || echo "Namespace $EKS_NAMESPACE already exists" + + - name: Deploy ${{ vars.APP_NAME }} to Kubernetes + run: | + helm upgrade --install ${{ vars.APP_NAME }} ./helm/app \ + --namespace ${{ vars.APP_NAME }}-${{ env.APP_ENV }} \ + --values ./helm/app/values.yaml \ + --values ./helm/app/values-${{ env.APP_ENV }}.yaml \ + --set imageRepo="ghcr.io/${{ steps.lower-repo.outputs.repository }}" \ + --set imageTag="${{ env.GITHUB_SHA_SHORT }}" \ + --set host=${{ env.APP_DOMAIN }} \ + --set appName=${{ vars.APP_NAME }} \ + --set ghcrSecret=${{ secrets.GHCR_SECRET }} \ + --set secrets.publicProxyKey=${{ secrets.NEXT_PUBLIC_MIXPANEL_TOKEN }} \ + --set secrets.publicMixPanelToken=${{ secrets.NEXT_PUBLIC_ANALYTICS_ENABLED }} \ + --set secrets.publicProxyKey=${{ secrets.NEXT_PUBLIC_ANALYTICS_ENABLED }} + + - name: Verify deployment + run: | + kubectl -n ${{ vars.APP_NAME }}-${{ env.APP_ENV }} rollout status deployment/${{ vars.APP_NAME }}-${{ env.APP_ENV }} + + - name: Verify TLS Certificate + run: | + kubectl describe certificate ${{ env.APP_DOMAIN }} -n ${{ vars.APP_NAME }}-${{ env.APP_ENV }} + + - name: Telegram Notify + uses: appleboy/telegram-action@v1.0.0 + if: success() && contains('${{ vars.ENABLE_DEPLOY_BOT }}', 1) + with: + to: ${{ secrets.TELEGRAM_DEPLOY_CHAT_ID }} + token: ${{ secrets.TELEGRAM_DEPLOY_TOKEN }} + format: markdown + message: | + πŸš‚ The application from repository [${{ steps.lower-repo.outputs.repository }}](https://github.com/${{ steps.lower-repo.outputs.repository }}) has been successfully deployed by [${{ github.actor }}](https://github.com/users/${{ github.actor }}) on ${{ env.APP_ENV }}. + + πŸ—οΈ [GitHub Actions Build](https://github.com/${{ steps.lower-repo.outputs.repository }}/actions/runs/${{ github.run_id }}) + 🐳 [Image](https://ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ env.GITHUB_SHA_SHORT }} + πŸ”— [Link](https://${{ env.APP_DOMAIN }}) + + - name: Telegram Notify + uses: appleboy/telegram-action@v1.0.0 + if: failure() + with: + to: ${{ secrets.TELEGRAM_DEPLOY_CHAT_ID }} + token: ${{ secrets.TELEGRAM_DEPLOY_TOKEN }} + format: markdown + message: | + 🚨Deploy of the application from repository [${{ steps.lower-repo.outputs.repository }}](https://github.com/${{ steps.lower-repo.outputs.repository }}) on ${{ env.APP_ENV }} has been failed. + + πŸ—οΈ [GitHub Actions Build](https://github.com/${{ steps.lower-repo.outputs.repository }}/actions/runs/${{ github.run_id }}) + 🐳 [Image](https://ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ env.GITHUB_SHA_SHORT }} + πŸ”— [Link](https://${{ env.APP_DOMAIN }}) + + + + + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..6170530 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/web-ide.iml b/.idea/web-ide.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/web-ide.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cabec61 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM node:21-alpine AS base + +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +EXPOSE 3000 +CMD ["npm", "start"] diff --git a/helm/app/Chart.yaml b/helm/app/Chart.yaml new file mode 100644 index 0000000..cf9ba02 --- /dev/null +++ b/helm/app/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: Node.js Chart +description: A Helm chart for deploying my Node.js application +version: 0.1.2 diff --git a/helm/app/templates/deployment.yaml b/helm/app/templates/deployment.yaml new file mode 100644 index 0000000..f79cf3f --- /dev/null +++ b/helm/app/templates/deployment.yaml @@ -0,0 +1,54 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.appName }}-{{ .Values.deployEnv}} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.appName }}-{{ .Values.deployEnv }} + release: prometheus-stack +spec: + replicas: {{ .Values.defaultReplicaCount }} + strategy: + type: RollingUpdate + selector: + matchLabels: + app: {{ .Values.appName }}-{{ .Values.deployEnv}} + template: + metadata: + labels: + app: {{ .Values.appName }}-{{ .Values.deployEnv}} + release: prometheus-stack + spec: + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app: {{ .Values.appName }}-{{ .Values.deployEnv }} + containers: + - name: {{ .Values.appName }}-{{ .Values.deployEnv}} + image: "{{ .Values.imageRepo }}:{{ .Values.imageTag }}" + env: + - name: APP_ENV + value: {{ .Values.deployEnv }} + - name: APP_VERSION + value: {{ .Values.appVersion | quote }} + - name: NEXT_PUBLIC_PROXY_KEY + value: {{ .Values.secrets.publicProxyKey | quote }} + - name: NEXT_PUBLIC_MIXPANEL_TOKEN + value: {{ .Values.secrets.publicMixPanelToken | quote }} + - name: NEXT_PUBLIC_ANALYTICS_ENABLED + value: {{ .Values.secrets.publicProxyKey | quote }} + ports: + - containerPort: {{ .Values.containerPort }} + resources: + limits: + cpu: {{ .Values.cpuLimit }} + memory: {{ .Values.memoryLimit }} + requests: + cpu: {{ .Values.cpuRequest }} + memory: {{ .Values.memoryRequest }} + imagePullPolicy: Always + imagePullSecrets: + - name: dockerconfigjson-github-com diff --git a/helm/app/templates/ghcr-secret.yaml b/helm/app/templates/ghcr-secret.yaml new file mode 100644 index 0000000..d126766 --- /dev/null +++ b/helm/app/templates/ghcr-secret.yaml @@ -0,0 +1,10 @@ +kind: Secret +type: kubernetes.io/dockerconfigjson +apiVersion: v1 +metadata: + name: dockerconfigjson-github-com + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.appName }}-{{ .Values.deployEnv}} +data: + .dockerconfigjson: {{ .Values.ghcrSecret }} \ No newline at end of file diff --git a/helm/app/templates/hpa.yaml b/helm/app/templates/hpa.yaml new file mode 100644 index 0000000..c5c85d2 --- /dev/null +++ b/helm/app/templates/hpa.yaml @@ -0,0 +1,25 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Values.appName }}-{{ .Values.deployEnv}} + namespace: {{ .Release.Namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.appName }}-{{ .Values.deployEnv}} + minReplicas: {{ .Values.minReplicas }} + maxReplicas: {{ .Values.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/helm/app/templates/ingress.yaml b/helm/app/templates/ingress.yaml new file mode 100644 index 0000000..3b33f35 --- /dev/null +++ b/helm/app/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{ if .Values.publicService }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Values.appName }}-{{ .Values.deployEnv}} + namespace: {{ .Release.Namespace }} + annotations: + kubernetes.io/ingress.class: "nginx" + nginx.ingress.kubernetes.io/ssl-redirect: "{{ .Values.sslRedirect }}" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "10s" + nginx.ingress.kubernetes.io/proxy-read-timeout: "15s" + nginx.ingress.kubernetes.io/proxy-send-timeout: "15s" + nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout http_502 http_503 http_504" + nginx.ingress.kubernetes.io/proxy-next-upstream-tries: "3" + {{- if eq .Values.deployEnv "canary" }} + nginx.ingress.kubernetes.io/canary: "true" + nginx.ingress.kubernetes.io/canary-weight: {{ .Values.canaryWeight | quote }} + {{- end }} + nginx.ingress.kubernetes.io/server-snippet: | + location ~ ^/(metrics|ready|health)$ { + return 403; + } + + labels: + release: prometheus-stack + app: {{ .Values.appName }}-{{ .Values.deployEnv}} +spec: + tls: + - hosts: + - {{ .Values.host }} + secretName: {{ .Values.host }} + rules: + - host: {{ .Values.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Values.appName }}-{{ .Values.deployEnv }} + port: + name: http +{{- end }} diff --git a/helm/app/templates/ns-resource-quota.yaml b/helm/app/templates/ns-resource-quota.yaml new file mode 100644 index 0000000..631e73d --- /dev/null +++ b/helm/app/templates/ns-resource-quota.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: + name: resource-quota + namespace: {{ .Release.Namespace }} +spec: + hard: + {{- if eq .Values.deployEnv "staging" }} + pods: "2" + services: "1" + requests.memory: "256Mi" + limits.cpu: "1" + limits.memory: "16Gi" + persistentvolumeclaims: "0" + {{- end }} + {{- if eq .Values.deployEnv "canary" }} + pods: "10" + limits.cpu: "4" + limits.memory: "8Gi" + persistentvolumeclaims: "0" + {{- end }} + {{- if eq .Values.deployEnv "production" }} + pods: "100" + limits.cpu: "8" + limits.memory: "16Gi" + persistentvolumeclaims: "0" + {{- end }} diff --git a/helm/app/templates/service-monitor.yaml b/helm/app/templates/service-monitor.yaml new file mode 100644 index 0000000..30b47f6 --- /dev/null +++ b/helm/app/templates/service-monitor.yaml @@ -0,0 +1,19 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ .Values.appName }}-{{ .Values.deployEnv}} + namespace: {{ .Release.Namespace }} + labels: + release: prometheus-stack +spec: + selector: + matchLabels: + app: {{ .Values.appName }}-{{ .Values.deployEnv}} + endpoints: + - port: http + interval: 30s + path: /metrics + - port: https + interval: 30s + path: /metrics + diff --git a/helm/app/templates/service.yaml b/helm/app/templates/service.yaml new file mode 100644 index 0000000..6a6d990 --- /dev/null +++ b/helm/app/templates/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.appName }}-{{ .Values.deployEnv}} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.appName }}-{{ .Values.deployEnv }} + release: prometheus-stack +spec: + type: ClusterIP + selector: + app: {{ .Values.appName }}-{{ .Values.deployEnv}} + ports: + - name: http + protocol: TCP + port: 80 + targetPort: {{ .Values.containerPort }} \ No newline at end of file diff --git a/helm/app/templates/tls-cert.yaml b/helm/app/templates/tls-cert.yaml new file mode 100644 index 0000000..b0aff17 --- /dev/null +++ b/helm/app/templates/tls-cert.yaml @@ -0,0 +1,15 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ .Values.appName }}-{{ .Values.deployEnv}} + namespace: {{ .Release.Namespace }} +spec: + secretName: {{ .Values.host }} + issuerRef: + kind: ClusterIssuer + name: {{ .Values.tlsIssuer }} + commonName: {{ .Values.host }} + duration: 2160h + renewBefore: 360h + dnsNames: + - {{ .Values.host }} diff --git a/helm/app/values-production.yaml b/helm/app/values-production.yaml new file mode 100644 index 0000000..dd66045 --- /dev/null +++ b/helm/app/values-production.yaml @@ -0,0 +1,9 @@ +deployEnv: production +defaultReplicaCount: 6 +imageTag: "production" + +minReplicas: 1 +maxReplicas: 1 + +memoryLimit: 120Mi +memoryRequest: 100Mi diff --git a/helm/app/values-staging.yaml b/helm/app/values-staging.yaml new file mode 100644 index 0000000..4c7c9ad --- /dev/null +++ b/helm/app/values-staging.yaml @@ -0,0 +1,9 @@ +deployEnv: staging +defaultReplicaCount: 6 +imageTag: "staging" + +minReplicas: 1 +maxReplicas: 1 + +memoryLimit: 120Mi +memoryRequest: 100Mi diff --git a/helm/app/values.yaml b/helm/app/values.yaml new file mode 100644 index 0000000..32d6ae5 --- /dev/null +++ b/helm/app/values.yaml @@ -0,0 +1,41 @@ +# app +appVersion: "0.1" + +# limits & requests +cpuLimit: "500m" +memoryLimit: "128Mi" +cpuRequest: "500m" +memoryRequest: "64Mi" + +# replicas +minReplicas: 2 +maxReplicas: 40 + +# docker +containerPort: 3000 +nodePort: 80 + +# from github deploy +imageRepo: "" +imageTag: "" +host: "" +appName: "" +ghcrSecret: "" + +tlsCert: "" +tlsKey: "" + + +# do not change +tlsIssuer: "letsencrypt" +certIssuingMode: false + +# http +publicService: true +sslRedirect: false + + +secrets: + publicProxyKey: "" + publicMixPanelToken: "" + secrets.publicProxyKey: "" From 5704d1a2733ef1ab6bb9a076c101863e636bcc34 Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Thu, 24 Oct 2024 20:36:26 +0530 Subject: [PATCH 2/4] feat(theme): add support for light theme --- commitlint.config.ts | 1 + .../project/NewProject/NewProject.module.scss | 8 +- .../shared/LogView/LogView.module.scss | 6 ++ src/components/shared/LogView/LogView.tsx | 2 +- .../shared/ThemeProvider/ThemeProvider.tsx | 74 +++++++++++++++++++ src/components/shared/ThemeProvider/index.ts | 1 + src/components/shared/index.ts | 1 + .../ProjectTemplate/ProjectTemplate.tsx | 9 ++- src/components/ui/icon/index.tsx | 8 +- .../workspace/ABIUi/ABIUi.module.scss | 34 +++++++-- .../BottomPanel/BottomPanel.module.scss | 6 +- .../BuildProject/BuildProject.module.scss | 9 ++- .../workspace/Editor/Editor.module.scss | 3 + src/components/workspace/Editor/Editor.tsx | 4 +- .../workspace/Tabs/Tabs.module.scss | 7 +- .../workspace/WorkSpace/WorkSpace.module.scss | 10 +-- .../WorkspaceSidebar.module.scss | 22 +++++- .../WorkspaceSidebar/WorkspaceSidebar.tsx | 29 +++++++- .../ManageProject/ManageProject.module.scss | 1 + src/interfaces/setting.interface.ts | 1 + src/pages/_app.tsx | 19 +---- src/state/IDE.context.tsx | 1 + src/styles/components/file-icons.scss | 13 +++- src/styles/components/project.scss | 2 +- src/styles/global.scss | 61 +++++++++++---- src/styles/lib/form.scss | 7 +- 26 files changed, 270 insertions(+), 69 deletions(-) create mode 100644 src/components/shared/ThemeProvider/ThemeProvider.tsx create mode 100644 src/components/shared/ThemeProvider/index.ts diff --git a/commitlint.config.ts b/commitlint.config.ts index 05556c9..7e26a14 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -38,6 +38,7 @@ const Configuration: UserConfig = { 'build', // Build scripts or configuration 'ci', // Continuous integration 'release', // Release related changes + 'theme', // Theme changes 'other', // Other changes ], ], diff --git a/src/components/project/NewProject/NewProject.module.scss b/src/components/project/NewProject/NewProject.module.scss index 6108965..00a076e 100644 --- a/src/components/project/NewProject/NewProject.module.scss +++ b/src/components/project/NewProject/NewProject.module.scss @@ -56,10 +56,7 @@ [class*='ant-radio-button-checked'] { border: solid 1px transparent; - background-image: linear-gradient(rgb(20, 20, 20), rgb(20, 20, 20)), - var(--primary-gradient); - background-origin: border-box; - background-clip: content-box, border-box; + background: var(--primary); border-radius: var(--border-radius); &::before { @@ -72,6 +69,9 @@ background: #000; z-index: -1; border-radius: inherit; + [data-theme='light'] & { + background: var(--primary); + } } } } diff --git a/src/components/shared/LogView/LogView.module.scss b/src/components/shared/LogView/LogView.module.scss index f99ef14..f0240b1 100644 --- a/src/components/shared/LogView/LogView.module.scss +++ b/src/components/shared/LogView/LogView.module.scss @@ -57,4 +57,10 @@ display: inline-block; margin-left: 0.5rem; } + [class*='xterm-cursor-outline'] { + outline-color: var(--text-color) !important; + } + [class*='xterm-cursor-bar'] { + box-shadow: 1px 0 0 var(--text-color) inset !important; + } } diff --git a/src/components/shared/LogView/LogView.tsx b/src/components/shared/LogView/LogView.tsx index cc1de19..7163d4b 100644 --- a/src/components/shared/LogView/LogView.tsx +++ b/src/components/shared/LogView/LogView.tsx @@ -37,7 +37,7 @@ const LogView: FC = ({ filter }) => { grey: '\x1b[38;5;243m', success: '\x1b[38;5;40m', error: '\x1b[38;5;196m', - warning: '\x1b[38;5;226m', + warning: '\x1b[38;5;214m', info: '\x1b[38;5;33m', reset: '\x1b[0m', }; diff --git a/src/components/shared/ThemeProvider/ThemeProvider.tsx b/src/components/shared/ThemeProvider/ThemeProvider.tsx new file mode 100644 index 0000000..ceed820 --- /dev/null +++ b/src/components/shared/ThemeProvider/ThemeProvider.tsx @@ -0,0 +1,74 @@ +import { ConfigProvider, theme as antdTheme } from 'antd'; +import { + ReactNode, + createContext, + useContext, + useEffect, + useState, +} from 'react'; + +type Theme = 'light' | 'dark'; + +interface ThemeContextProps { + theme: Theme; + toggleTheme: () => void; +} + +export const ThemeContext = createContext( + undefined, +); + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; + +export const ThemeProvider = ({ children }: { children: ReactNode }) => { + // Initialize theme based on local storage or system preference + const [theme, setTheme] = useState(() => { + if (typeof window === 'undefined') { + return 'dark'; + } + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + return savedTheme as Theme; + } + const prefersDark = window.matchMedia( + '(prefers-color-scheme: dark)', + ).matches; + return prefersDark ? 'dark' : 'light'; + }); + + // Apply the theme to the HTML element and save it in localStorage + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('theme', theme); + }, [theme]); + + const toggleTheme = () => { + setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); + }; + + // Ant Design's theme configuration + const antdConfig = { + token: { + colorPrimary: '#0098ea', + colorError: '#C84075', + fontFamily: 'var(--font-body)', + borderRadius: 4, + }, + algorithm: + theme === 'dark' ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm, + }; + + return ( + + {children} + + ); +}; + +export default ThemeProvider; diff --git a/src/components/shared/ThemeProvider/index.ts b/src/components/shared/ThemeProvider/index.ts new file mode 100644 index 0000000..c3d210e --- /dev/null +++ b/src/components/shared/ThemeProvider/index.ts @@ -0,0 +1 @@ +export { ThemeContext, default, useTheme } from './ThemeProvider'; diff --git a/src/components/shared/index.ts b/src/components/shared/index.ts index 94d5a8a..21bfb55 100644 --- a/src/components/shared/index.ts +++ b/src/components/shared/index.ts @@ -1,2 +1,3 @@ export { default as Layout } from './Layout'; export { default as LogView } from './LogView'; +export { default as ThemeProvider } from './ThemeProvider'; diff --git a/src/components/template/ProjectTemplate/ProjectTemplate.tsx b/src/components/template/ProjectTemplate/ProjectTemplate.tsx index 4ce3ce8..65e166e 100644 --- a/src/components/template/ProjectTemplate/ProjectTemplate.tsx +++ b/src/components/template/ProjectTemplate/ProjectTemplate.tsx @@ -1,5 +1,6 @@ /* eslint-disable react/no-children-prop */ import { NewProject } from '@/components/project'; +import { useTheme } from '@/components/shared/ThemeProvider'; import AppIcon from '@/components/ui/icon'; import { AppConfig } from '@/config/AppConfig'; import { projectExamples } from '@/constant/projectExamples'; @@ -9,7 +10,10 @@ import Link from 'next/link'; import { FC, useEffect, useState } from 'react'; import Markdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { oneDark as darkTheme } from 'react-syntax-highlighter/dist/cjs/styles/prism'; +import { + oneDark as darkTheme, + oneLight as lightTheme, +} from 'react-syntax-highlighter/dist/cjs/styles/prism'; import s from './ProjectTemplate.module.scss'; function LinkRenderer({ @@ -35,6 +39,7 @@ const ProjectTemplate: FC = () => { contract: string; content: string; }>({ contract: '', content: '' }); + const { theme } = useTheme(); const getContent = async () => { const link = examples[currentExample].link; @@ -166,7 +171,7 @@ const ProjectTemplate: FC = () => { PreTag="div" children={String(children).replace(/\n$/, '')} language={match[1]} - style={darkTheme} + style={theme === 'dark' ? darkTheme : lightTheme} /> ) : ( diff --git a/src/components/ui/icon/index.tsx b/src/components/ui/icon/index.tsx index c7de7a0..a1147e2 100644 --- a/src/components/ui/icon/index.tsx +++ b/src/components/ui/icon/index.tsx @@ -12,7 +12,7 @@ import { import { BsShare } from 'react-icons/bs'; import { BsFillPlayFill } from 'react-icons/bs'; -import { FaRegClone } from 'react-icons/fa'; +import { FaMoon, FaRegClone, FaSun } from 'react-icons/fa'; import { FiEdit2, FiEye } from 'react-icons/fi'; import { GoTriangleDown, GoTriangleRight, GoTriangleUp } from 'react-icons/go'; import { GrClear } from 'react-icons/gr'; @@ -69,7 +69,9 @@ export type AppIconType = | 'Import' | 'Reload' | 'Share' - | 'Save'; + | 'Save' + | 'Moon' + | 'Sun'; export interface AppIconInterface { name: AppIconType; @@ -110,6 +112,8 @@ const Components = { Reload: AiOutlineReload, Share: BsShare, Save: AiOutlineSave, + Moon: FaMoon, + Sun: FaSun, }; const AppIcon: FC = ({ name, className = '' }) => { diff --git a/src/components/workspace/ABIUi/ABIUi.module.scss b/src/components/workspace/ABIUi/ABIUi.module.scss index 4eed051..35bc504 100644 --- a/src/components/workspace/ABIUi/ABIUi.module.scss +++ b/src/components/workspace/ABIUi/ABIUi.module.scss @@ -12,6 +12,9 @@ height: 1px; margin: 0 auto; background-color: #373636; + [data-theme='light'] & { + background-color: #e0e0e0; + } } &.tact { display: flex; @@ -30,6 +33,9 @@ background: rgba(14, 14, 16, 0.9); border-radius: 5px; padding: 5px; + [data-theme='light'] & { + background: rgba(255, 255, 255, 0.9); + } > div { border-left: 0 !important; @@ -62,27 +68,40 @@ border: 0; box-shadow: none; background: #232222; + [data-theme='light'] & { + background: #d6d6d6; + } + &:hover { + color: var(--text-color); + background-color: var(--grey--50); + } } } &.Setter { .btnAction { border: solid 1px transparent; - background-image: linear-gradient(rgb(20, 20, 20), rgb(20, 20, 20)), - var(--primary-gradient); - background-origin: border-box; - background-clip: content-box, border-box; + background: var(--primary); box-shadow: none; z-index: 0; + &:hover { border-color: transparent; - color: #fff; + color: var(--text-color); + &:before { background: #101010; + [data-theme='light'] & { + background: var(--btn-primary-hover); + } } } + & span, + &:hover span [data-theme='light'] & { + color: #fff; + } &:disabled { opacity: 0.5; - color: #fff; + color: var(--text-color); } &:before { @@ -95,6 +114,9 @@ background: #000; z-index: -1; border-radius: inherit; + [data-theme='light'] & { + background: var(--primary); + } } } } diff --git a/src/components/workspace/BottomPanel/BottomPanel.module.scss b/src/components/workspace/BottomPanel/BottomPanel.module.scss index ad26933..fa08640 100644 --- a/src/components/workspace/BottomPanel/BottomPanel.module.scss +++ b/src/components/workspace/BottomPanel/BottomPanel.module.scss @@ -1,5 +1,5 @@ .root { - background-color: #181717; + background-color: var(--dark-800); height: 100%; z-index: 1000; position: relative; @@ -43,11 +43,11 @@ justify-content: center; .icon { path { - stroke: #fff; + stroke: var(--text-color); } } &:hover { - background-color: var(--grey); + background-color: var(--grey--70); } } } diff --git a/src/components/workspace/BuildProject/BuildProject.module.scss b/src/components/workspace/BuildProject/BuildProject.module.scss index aa7085a..be4969a 100644 --- a/src/components/workspace/BuildProject/BuildProject.module.scss +++ b/src/components/workspace/BuildProject/BuildProject.module.scss @@ -4,7 +4,7 @@ height: 100vh; overflow-y: auto; .heading { - color: #ccc; + color: var(--color-light); text-align: center; display: block; font-size: 0.875rem; @@ -28,6 +28,9 @@ background: rgba(14, 14, 16, 0.9); border-radius: 5px; padding: 2px; + [data-theme='light'] & { + background: rgba(255, 255, 255, 0.9); + } > div { border-left: 0 !important; } @@ -42,11 +45,11 @@ } .connectedWallet { font-size: 0.8rem; - color: #ccc; + color: var(--color-light); margin-top: 1rem; span { font-size: 0.9rem; - color: #fff; + color: var(--text-color); } } .contractAddress { diff --git a/src/components/workspace/Editor/Editor.module.scss b/src/components/workspace/Editor/Editor.module.scss index c8f1aad..67b030c 100644 --- a/src/components/workspace/Editor/Editor.module.scss +++ b/src/components/workspace/Editor/Editor.module.scss @@ -14,6 +14,9 @@ justify-content: space-between; font-size: 0.8rem; padding: 0.2rem 1rem; + [data-theme='light'] & { + background-color: rgba(255, 255, 255, 0.7); + } .vimStatuBar { font-family: monospace; } diff --git a/src/components/workspace/Editor/Editor.tsx b/src/components/workspace/Editor/Editor.tsx index 2ff310c..56d0d91 100644 --- a/src/components/workspace/Editor/Editor.tsx +++ b/src/components/workspace/Editor/Editor.tsx @@ -7,6 +7,7 @@ import EditorDefault, { loader } from '@monaco-editor/react'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { FC, useEffect, useRef, useState } from 'react'; // import { useLatest } from 'react-use'; +import { useTheme } from '@/components/shared/ThemeProvider'; import { useFile, useFileTab } from '@/hooks'; import { useProject } from '@/hooks/projectV2.hooks'; import { useLatest } from 'react-use'; @@ -23,6 +24,7 @@ const Editor: FC = ({ className = '' }) => { const { activeProject } = useProject(); const { getFile, saveFile: storeFileContent } = useFile(); const { fileTab, updateFileDirty } = useFileTab(); + const { theme } = useTheme(); const { isFormatOnSave, getSettingStateByKey } = useSettingAction(); @@ -249,7 +251,7 @@ const Editor: FC = ({ className = '' }) => {