Compare commits
15
Commits
3f344fa1a8
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab87001c77 | ||
|
|
ef4db213c7 | ||
|
|
9ba09fb55b | ||
|
|
4b4e31a55f | ||
|
|
9b2ae99df6 | ||
|
|
af9f92478e | ||
|
|
affb1b1ad8 | ||
|
|
a8d46921f9 | ||
|
|
5eee968d1c | ||
|
|
dcee392e02 | ||
|
|
8803dfa49a | ||
|
|
52a3bb9ab7 | ||
|
|
ed5eead80f | ||
|
|
df6cb33731 | ||
|
|
f315ee56a1 |
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.next
|
||||
out
|
||||
docs
|
||||
.git
|
||||
.gitignore
|
||||
npm-debug.log
|
||||
@@ -0,0 +1,99 @@
|
||||
name: Build & Deploy (develop)
|
||||
|
||||
# Runs on:
|
||||
# - Pull requests targeting develop
|
||||
# - Direct pushes to develop
|
||||
# main is intentionally NOT included yet (no prod target configured)
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Resolve commit SHA
|
||||
id: vars
|
||||
run: |
|
||||
# For PRs, gitea.sha is a temporary merge commit — use the real
|
||||
# PR head commit instead so tags map to actual git history.
|
||||
FULL_SHA="${{ gitea.event.pull_request.head.sha || gitea.sha }}"
|
||||
echo "sha=${FULL_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build Docker image (tagged by commit SHA)
|
||||
run: |
|
||||
docker build -t robot-market:${{ steps.vars.outputs.sha }} .
|
||||
|
||||
- name: Save previous tag for rollback
|
||||
run: |
|
||||
if [ -f /opt/robot-market-front/.env ]; then
|
||||
cp /opt/robot-market-front/.env /opt/robot-market-front/.env.previous
|
||||
fi
|
||||
|
||||
- name: Point compose at this build
|
||||
run: |
|
||||
echo "IMAGE_TAG=${{ steps.vars.outputs.sha }}" > /opt/robot-market-front/.env
|
||||
|
||||
- name: Deploy via compose
|
||||
run: |
|
||||
docker compose --env-file /opt/robot-market-front/.env \
|
||||
-f /opt/robot-market-front/docker-compose.yml \
|
||||
up -d --force-recreate
|
||||
|
||||
- name: Health check (retries for ~30s)
|
||||
run: |
|
||||
for i in $(seq 1 15); do
|
||||
if docker exec robot-market node -e "
|
||||
require('http').get('http://localhost:3000', res => {
|
||||
process.exit(res.statusCode < 400 ? 0 : 1);
|
||||
}).on('error', () => process.exit(1));
|
||||
" 2>/dev/null; then
|
||||
echo "Container is healthy"
|
||||
exit 0
|
||||
fi
|
||||
echo "Not ready yet, retrying..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Health check failed after 30s"
|
||||
exit 1
|
||||
|
||||
- name: Roll back to previous version on failed health check
|
||||
if: failure()
|
||||
run: |
|
||||
if [ -f /opt/robot-market-front/.env.previous ]; then
|
||||
cp /opt/robot-market-front/.env.previous /opt/robot-market-front/.env
|
||||
docker compose --env-file /opt/robot-market-front/.env \
|
||||
-f /opt/robot-market-front/docker-compose.yml \
|
||||
up -d --force-recreate
|
||||
echo "Rolled back to previous image."
|
||||
else
|
||||
echo "No previous deploy recorded — nothing to roll back to."
|
||||
fi
|
||||
|
||||
- name: Clean up dangling (untagged) layers
|
||||
run: docker image prune -f
|
||||
|
||||
- name: Prune old SHA-tagged images (keep last 5)
|
||||
run: |
|
||||
KEEP=5
|
||||
CURRENT_TAG=$(grep IMAGE_TAG /opt/robot-market-front/.env 2>/dev/null | cut -d= -f2)
|
||||
PREVIOUS_TAG=$(grep IMAGE_TAG /opt/robot-market-front/.env.previous 2>/dev/null | cut -d= -f2)
|
||||
|
||||
docker images robot-market --format '{{.Tag}} {{.CreatedAt}}' \
|
||||
| sort -t" " -k2 -r \
|
||||
| tail -n +$((KEEP + 1)) \
|
||||
| cut -f1 \
|
||||
| while read -r tag; do
|
||||
if [ "$tag" != "$CURRENT_TAG" ] && [ "$tag" != "$PREVIOUS_TAG" ] && [ "$tag" != "<none>" ]; then
|
||||
echo "Removing old image robot-market:$tag"
|
||||
docker rmi "robot-market:$tag" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Rollback (develop)
|
||||
|
||||
# Trigger manually from the Gitea Actions UI.
|
||||
# Leave "sha" empty to roll back to the previous deploy.
|
||||
# Fill "sha" in (short 7-char form, e.g. a3f9c21) to roll back to a specific past build.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
sha:
|
||||
description: "Short commit SHA to roll back to (empty = previous deploy)"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
rollback:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine target tag
|
||||
run: |
|
||||
if [ -n "${{ gitea.event.inputs.sha }}" ]; then
|
||||
TAG="${{ gitea.event.inputs.sha }}"
|
||||
else
|
||||
TAG=$(grep IMAGE_TAG /opt/robot-market-front/.env.previous | cut -d= -f2)
|
||||
fi
|
||||
echo "Rolling back to: $TAG"
|
||||
echo "IMAGE_TAG=$TAG" > /opt/robot-market-front/.env
|
||||
|
||||
- name: Verify that image actually exists locally
|
||||
run: |
|
||||
TAG=$(grep IMAGE_TAG /opt/robot-market-front/.env | cut -d= -f2)
|
||||
docker image inspect robot-market:$TAG > /dev/null
|
||||
|
||||
- name: Redeploy target image
|
||||
run: |
|
||||
docker compose --env-file /opt/robot-market-front/.env \
|
||||
-f /opt/robot-market-front/docker-compose.yml \
|
||||
up -d --force-recreate
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Runner Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
workflow_dispatch: # lets you trigger it manually from the UI too
|
||||
|
||||
jobs:
|
||||
hello:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Print basic info
|
||||
run: |
|
||||
echo "✅ Runner is working!"
|
||||
echo "Hostname: $(hostname)"
|
||||
echo "Date: $(date)"
|
||||
echo "User: $(whoami)"
|
||||
pwd
|
||||
ls -la
|
||||
|
||||
- name: Test env variables
|
||||
run: |
|
||||
echo "Branch: ${{ github.ref_name }}"
|
||||
echo "Commit SHA: ${{ github.sha }}"
|
||||
echo "Actor: ${{ github.actor }}"
|
||||
|
||||
- name: Simulate a build step
|
||||
run: |
|
||||
mkdir -p build
|
||||
echo "Build artifact content" > build/output.txt
|
||||
cat build/output.txt
|
||||
Generated
+25
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JsonSchemaMappingsProjectConfiguration">
|
||||
<state>
|
||||
<map>
|
||||
<entry key="GitHub Workflow">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
<option name="name" value="GitHub Workflow" />
|
||||
<option name="relativePathToSchema" value="https://www.schemastore.org/github-workflow.json" />
|
||||
<option name="applicationDefined" value="true" />
|
||||
<option name="patterns">
|
||||
<list>
|
||||
<Item>
|
||||
<option name="path" value=".gitea/workflows/test.yml" />
|
||||
</Item>
|
||||
</list>
|
||||
</option>
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</state>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+116
-59
@@ -4,8 +4,9 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="bb7b50a5-be9c-4cd7-a05b-979f0e91b173" name="Changes" comment="update">
|
||||
<list default="true" id="bb7b50a5-be9c-4cd7-a05b-979f0e91b173" name="Changes" comment="initial new branch">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/data/content.js" beforeDir="false" afterPath="$PROJECT_DIR$/data/content.js" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -13,6 +14,11 @@
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="develop" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="JsbtTreeLayoutManager">
|
||||
@@ -22,61 +28,61 @@
|
||||
</component>
|
||||
<component name="NextEditCompletionFeaturesState">
|
||||
<decayedCancelled>
|
||||
<entry key="MS100" value="1.9180577791458209" />
|
||||
<entry key="MS500" value="4.688644388100796" />
|
||||
<entry key="S2" value="8.26749701184336" />
|
||||
<entry key="S5" value="10.565752377401568" />
|
||||
<entry key="S10" value="11.665306157952212" />
|
||||
<entry key="S30" value="12.525747261610114" />
|
||||
<entry key="S60" value="12.758981623001501" />
|
||||
<entry key="M2" value="12.87850025822862" />
|
||||
<entry key="M5" value="12.951160086704387" />
|
||||
<entry key="M10" value="12.975539848697636" />
|
||||
<entry key="M15" value="12.983684286049057" />
|
||||
<entry key="M30" value="12.99183766624342" />
|
||||
<entry key="H1" value="12.995917713255967" />
|
||||
<entry key="H2" value="12.997958576577691" />
|
||||
<entry key="H4" value="12.99897921826579" />
|
||||
<entry key="D1" value="12.999829859984649" />
|
||||
<entry key="W1" value="12.999975694045316" />
|
||||
<entry key="MS100" value="1.7659734554897204" />
|
||||
<entry key="MS500" value="2.6200917385797573" />
|
||||
<entry key="S2" value="2.895672466750576" />
|
||||
<entry key="S5" value="2.9711768722246337" />
|
||||
<entry key="S10" value="3.095911797960576" />
|
||||
<entry key="S30" value="3.6130929483281937" />
|
||||
<entry key="S60" value="4.862542006443887" />
|
||||
<entry key="M2" value="8.222118193057797" />
|
||||
<entry key="M5" value="14.352669475563026" />
|
||||
<entry key="M10" value="18.025488407782152" />
|
||||
<entry key="M15" value="19.665360290707866" />
|
||||
<entry key="M30" value="22.62190726116564" />
|
||||
<entry key="H1" value="26.416500535541072" />
|
||||
<entry key="H2" value="30.051114228090462" />
|
||||
<entry key="H4" value="32.65287419479587" />
|
||||
<entry key="D1" value="35.38129255958758" />
|
||||
<entry key="W1" value="35.9099877286286" />
|
||||
</decayedCancelled>
|
||||
<decayedSelected>
|
||||
<entry key="MS100" value="0.0" />
|
||||
<entry key="MS500" value="0.0" />
|
||||
<entry key="S2" value="0.0" />
|
||||
<entry key="S5" value="0.0" />
|
||||
<entry key="S10" value="0.0" />
|
||||
<entry key="S30" value="0.0" />
|
||||
<entry key="S60" value="0.0" />
|
||||
<entry key="M2" value="0.0" />
|
||||
<entry key="M5" value="0.0" />
|
||||
<entry key="M10" value="0.0" />
|
||||
<entry key="M15" value="0.0" />
|
||||
<entry key="M30" value="0.0" />
|
||||
<entry key="H1" value="0.0" />
|
||||
<entry key="H2" value="0.0" />
|
||||
<entry key="H4" value="0.0" />
|
||||
<entry key="D1" value="0.0" />
|
||||
<entry key="W1" value="0.0" />
|
||||
<entry key="MS500" value="1.5499348360687339E-164" />
|
||||
<entry key="S2" value="1.1157794535925266E-41" />
|
||||
<entry key="S5" value="4.1594062872493325E-17" />
|
||||
<entry key="S10" value="6.449345925944224E-9" />
|
||||
<entry key="S30" value="0.0018613950614188026" />
|
||||
<entry key="S60" value="0.043143887880194604" />
|
||||
<entry key="M2" value="0.20771106826597993" />
|
||||
<entry key="M5" value="0.533315069904893" />
|
||||
<entry key="M10" value="0.7302842391185045" />
|
||||
<entry key="M15" value="0.8109510091490459" />
|
||||
<entry key="M30" value="0.9005281834285062" />
|
||||
<entry key="H1" value="0.9489616343290734" />
|
||||
<entry key="H2" value="0.9741466184969663" />
|
||||
<entry key="H4" value="0.986988661787442" />
|
||||
<entry key="D1" value="0.9978195926881375" />
|
||||
<entry key="W1" value="0.9996882217756649" />
|
||||
</decayedSelected>
|
||||
<decayedShown>
|
||||
<entry key="MS100" value="1.7420918727122994" />
|
||||
<entry key="MS500" value="4.512290317154382" />
|
||||
<entry key="S2" value="8.19204120547899" />
|
||||
<entry key="S5" value="10.528482205836237" />
|
||||
<entry key="S10" value="11.645001964515167" />
|
||||
<entry key="S30" value="12.518545816831239" />
|
||||
<entry key="S60" value="12.755322274489146" />
|
||||
<entry key="M2" value="12.876655570534636" />
|
||||
<entry key="M5" value="12.950418561706101" />
|
||||
<entry key="M10" value="12.97516847394418" />
|
||||
<entry key="M15" value="12.98343656653061" />
|
||||
<entry key="M30" value="12.991713738236065" />
|
||||
<entry key="H1" value="12.995855732176478" />
|
||||
<entry key="H2" value="12.997927581767277" />
|
||||
<entry key="H4" value="12.998963719792695" />
|
||||
<entry key="D1" value="12.999827276757467" />
|
||||
<entry key="W1" value="12.999975325009228" />
|
||||
<entry key="MS100" value="1.6363299214720612" />
|
||||
<entry key="MS500" value="2.580440430635064" />
|
||||
<entry key="S2" value="2.884654206650678" />
|
||||
<entry key="S5" value="2.9666305108034594" />
|
||||
<entry key="S10" value="3.0934710562664876" />
|
||||
<entry key="S30" value="3.6138341741583195" />
|
||||
<entry key="S60" value="4.903918162005831" />
|
||||
<entry key="M2" value="8.426649355082025" />
|
||||
<entry key="M5" value="14.882896956006677" />
|
||||
<entry key="M10" value="18.75367770497998" />
|
||||
<entry key="M15" value="20.474761353109788" />
|
||||
<entry key="M30" value="23.521564444287325" />
|
||||
<entry key="H1" value="27.364990222969524" />
|
||||
<entry key="H2" value="31.025011433737856" />
|
||||
<entry key="H4" value="33.639733831212105" />
|
||||
<entry key="D1" value="36.37908993480534" />
|
||||
<entry key="W1" value="36.90967275725512" />
|
||||
</decayedShown>
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
@@ -90,20 +96,21 @@
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"ASKED_MARK_IGNORED_FILES_AS_EXCLUDED": "true",
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||
"git-widget-placeholder": "main",
|
||||
"javascript.nodejs.core.library.configured.version": "26.3.0",
|
||||
"javascript.nodejs.core.library.typings.version": "26.1.2",
|
||||
"git-widget-placeholder": "develop",
|
||||
"javascript.nodejs.core.library.configured.version": "24.20.0",
|
||||
"javascript.nodejs.core.library.typings.version": "24.13.3",
|
||||
"javascript.preferred.runtime.type.id": "node",
|
||||
"js.debugger.nextJs.config.created.client": "true",
|
||||
"js.debugger.nextJs.config.created.server": "true",
|
||||
"kronk.content.tree.expanded.paths": "",
|
||||
"last_opened_file_path": "D:/Project/GitHubRepo/robot-market-landing",
|
||||
"last_opened_file_path": "/home/hamed/Documents/Projects/GItLabRepo/device_web",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
@@ -139,7 +146,7 @@
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-WS-261.27258.45" />
|
||||
<option value="bundled-js-predefined-d6986cc7102b-4abd2a77f808-JavaScript-WS-262.8665.259" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
@@ -173,7 +180,14 @@
|
||||
<workItem from="1787980069071" duration="1369000" />
|
||||
<workItem from="1787993488084" duration="706000" />
|
||||
<workItem from="1788148958104" duration="1576000" />
|
||||
<workItem from="1788244010046" duration="1115000" />
|
||||
<workItem from="1788244010046" duration="3900000" />
|
||||
<workItem from="1788412255571" duration="40000" />
|
||||
<workItem from="1788412435362" duration="4637000" />
|
||||
<workItem from="1788423298952" duration="3393000" />
|
||||
<workItem from="1788580635152" duration="776000" />
|
||||
<workItem from="1788592387411" duration="8000" />
|
||||
<workItem from="1788592424522" duration="13793000" />
|
||||
<workItem from="1788670304118" duration="1889000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="update">
|
||||
<option name="closed" value="true" />
|
||||
@@ -359,7 +373,47 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1787052543722</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="24" />
|
||||
<task id="LOCAL-00024" summary="update">
|
||||
<option name="closed" value="true" />
|
||||
<created>1788245193637</created>
|
||||
<option name="number" value="00024" />
|
||||
<option name="presentableId" value="LOCAL-00024" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1788245193637</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00025" summary="update">
|
||||
<option name="closed" value="true" />
|
||||
<created>1788418070013</created>
|
||||
<option name="number" value="00025" />
|
||||
<option name="presentableId" value="LOCAL-00025" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1788418070013</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00026" summary="feat:dockerized & test actions add">
|
||||
<option name="closed" value="true" />
|
||||
<created>1788601868240</created>
|
||||
<option name="number" value="00026" />
|
||||
<option name="presentableId" value="LOCAL-00026" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1788601868240</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00027" summary="initial new branch">
|
||||
<option name="closed" value="true" />
|
||||
<created>1788682674027</created>
|
||||
<option name="number" value="00027" />
|
||||
<option name="presentableId" value="LOCAL-00027" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1788682674027</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00028" summary="initial new branch">
|
||||
<option name="closed" value="true" />
|
||||
<created>1788682678051</created>
|
||||
<option name="number" value="00028" />
|
||||
<option name="presentableId" value="LOCAL-00028" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1788682678051</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="29" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -371,6 +425,9 @@
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="ui : change the position of header in Hero Section" />
|
||||
<MESSAGE value="update" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="update" />
|
||||
<MESSAGE value="feat:dockerized & test actions add" />
|
||||
<MESSAGE value="feat:dockerized & test actions added" />
|
||||
<MESSAGE value="initial new branch" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="initial new branch" />
|
||||
</component>
|
||||
</project>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
push:
|
||||
git push origin HEAD
|
||||
git push gitea HEAD
|
||||
git push develop HEAD
|
||||
+7
-6
@@ -21,27 +21,28 @@ export default function HomePage() {
|
||||
|
||||
<CartDrawer/>
|
||||
|
||||
<ChatWidget/>
|
||||
{/*faz 1*/}
|
||||
{/*<ChatWidget/>*/}
|
||||
|
||||
<HeroSection/>
|
||||
|
||||
<ShowcaseSection/>
|
||||
|
||||
<TrustedBySection/>
|
||||
{/*<TrustedBySection/>*/}
|
||||
|
||||
<FeaturesSection/>
|
||||
|
||||
<AboutSection/>
|
||||
|
||||
<CatalogSection/>
|
||||
{/*<CatalogSection/>*/}
|
||||
|
||||
<LocationsSection/>
|
||||
|
||||
<IntroVideoSection/>
|
||||
{/*<IntroVideoSection/>*/}
|
||||
|
||||
<MapSection/>
|
||||
{/*<MapSection/>*/}
|
||||
|
||||
<TicketForm/>
|
||||
{/*<TicketForm/>*/}
|
||||
|
||||
<Footer/>
|
||||
</main>
|
||||
|
||||
+30
-29
@@ -2,38 +2,39 @@
|
||||
|
||||
// سکشن معرفی شرکت با متن بلند؛ بهخاطر طولانی بودن متن، یک دکمه «ادامه مطلب» برای باز/بسته کردن دارد.
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
import {useState} from 'react';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
|
||||
export default function AboutSection() {
|
||||
const { t } = useLanguage();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const visibleParagraphs = expanded ? t.about.paragraphs : t.about.paragraphs.slice(0, 2);
|
||||
const {t} = useLanguage();
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const visibleParagraphs = expanded ? t.about.paragraphs : t.about.paragraphs.slice(0, 2);
|
||||
|
||||
return (
|
||||
<section id="about" className="relative px-6 md:px-16 py-20 md:py-28 bg-elevated border-t border-subtle">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.about.eyebrow}</span>
|
||||
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3 mb-8">
|
||||
{t.about.legalName}
|
||||
</h2>
|
||||
return (
|
||||
<section id="about" className="relative px-6 md:px-16 py-20 md:py-28 bg-elevated border-t border-subtle">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.about.eyebrow}</span>
|
||||
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3 mb-8">
|
||||
{t.about.legalName}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col gap-5">
|
||||
{visibleParagraphs.map((p, i) => (
|
||||
<p key={i} className="text-secondary leading-8 text-base md:text-lg text-justify">
|
||||
{p}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-5">
|
||||
{visibleParagraphs.map((p, i) => (
|
||||
<p key={i} className="text-secondary leading-8 text-base md:text-lg text-justify">
|
||||
{p}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
className="mt-6 inline-flex items-center gap-2 text-brand-400 hover:text-brand-300 text-sm font-semibold transition-colors"
|
||||
>
|
||||
{expanded ? t.about.less : t.about.more}
|
||||
<span className={`transition-transform ${expanded ? 'rotate-180' : ''}`}>˅</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
{/* faz 1 */}
|
||||
{/* <button
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
className="mt-6 inline-flex items-center gap-2 text-brand-400 hover:text-brand-300 text-sm font-semibold transition-colors"
|
||||
>
|
||||
{expanded ? t.about.less : t.about.more}
|
||||
<span className={`transition-transform ${expanded ? 'rotate-180' : ''}`}>˅</span>
|
||||
</button>*/}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function CartDrawer() {
|
||||
// api() <===> todo : use this func to fetch ...
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
message.info(t.cart.checkoutToast);
|
||||
await message.info(t.cart.checkoutToast);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -28,6 +28,7 @@ export default function CatalogSection() {
|
||||
image: item.image,
|
||||
})
|
||||
);
|
||||
|
||||
message.success(t.catalog.addToast(item.name));
|
||||
};
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@ export default function Footer() {
|
||||
<li><a href="#hero" className="hover:text-brand-400">{t.nav.home}</a></li>
|
||||
<li><a href="#showcase" className="hover:text-brand-400">{t.nav.showcase}</a></li>
|
||||
<li><a href="#about" className="hover:text-brand-400">{t.nav.about}</a></li>
|
||||
<li><a href="#catalog" className="hover:text-brand-400">{t.nav.catalog}</a></li>
|
||||
{/*<li><a href="#catalog" className="hover:text-brand-400">{t.nav.catalog}</a></li>*/}
|
||||
<li><a href="#locations" className="hover:text-brand-400">{t.nav.locations}</a></li>
|
||||
<li><a href="#video" className="hover:text-brand-400">{t.nav.video}</a></li>
|
||||
<li><a href="#map" className="hover:text-brand-400">{t.nav.map}</a></li>
|
||||
<li><a href="#support" className="hover:text-brand-400">{t.nav.support}</a></li>
|
||||
{/*<li><a href="#video" className="hover:text-brand-400">{t.nav.video}</a></li>*/}
|
||||
{/*<li><a href="#map" className="hover:text-brand-400">{t.nav.map}</a></li>*/}
|
||||
{/*<li><a href="#support" className="hover:text-brand-400">{t.nav.support}</a></li>*/}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ export default function Header() {
|
||||
{href: '#hero', label: t.nav.home},
|
||||
{href: '#showcase', label: t.nav.showcase},
|
||||
{href: '#about', label: t.nav.about},
|
||||
{href: '#catalog', label: t.nav.catalog},
|
||||
// {href: '#catalog', label: t.nav.catalog},
|
||||
{href: '#locations', label: t.nav.locations},
|
||||
{href: '#video', label: t.nav.video},
|
||||
{href: '#map', label: t.nav.map},
|
||||
{href: '#support', label: t.nav.support},
|
||||
// {href: '#video', label: t.nav.video},
|
||||
// {href: '#map', label: t.nav.map},
|
||||
// {href: '#support', label: t.nav.support},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,7 +75,8 @@ export default function Header() {
|
||||
{t.nav.login}
|
||||
</Button>
|
||||
|
||||
<Badge count={cartCount} size="small" offset={[-2, 2]}>
|
||||
{/* faz 1 */}
|
||||
{/* <Badge count={cartCount} size="small" offset={[-2, 2]}>
|
||||
<Button
|
||||
shape="circle"
|
||||
size="large"
|
||||
@@ -83,7 +84,7 @@ export default function Header() {
|
||||
onClick={() => dispatch(openDrawer())}
|
||||
aria-label="cart"
|
||||
/>
|
||||
</Badge>
|
||||
</Badge>*/}
|
||||
|
||||
<button
|
||||
className="xl:hidden text-brand-400 text-2xl"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client';
|
||||
|
||||
// دکمه سوییچ حالت روشن/تیره.
|
||||
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { SunOutlined, MoonOutlined } from '@ant-design/icons';
|
||||
|
||||
@@ -101,9 +101,9 @@ export default function VendingCard({machine, onQuickView}) {
|
||||
<p className="text-xl md:text-2xl font-bold text-primary mt-2">{machine.priceLabel}</p>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2 mt-2 ">
|
||||
<Button type="primary" size="large" icon={<ShoppingCartOutlined/>} onClick={handleAddToCart}>
|
||||
{/* <Button type="primary" size="large" icon={<ShoppingCartOutlined/>} onClick={handleAddToCart}>
|
||||
{t.machineActions.addToCart}
|
||||
</Button>
|
||||
</Button>*/}
|
||||
<Button size="large" icon={<EyeOutlined/>} onClick={() => onQuickView?.(machine)}>
|
||||
{t.machineActions.quickView}
|
||||
</Button>
|
||||
|
||||
+12
-12
@@ -9,8 +9,8 @@ const machinesFa = [
|
||||
id: 'rm-35',
|
||||
name: 'روبات مارکت ۳۵ کانال',
|
||||
shortLabel: '۳۵ کانال',
|
||||
priceLabel: '۳,۸۶۰,۰۰۰,۰۰۰ ریال',
|
||||
priceNumeric: 3860000000,
|
||||
priceLabel: '4,200,000,000 ریال',
|
||||
priceNumeric: 4200000000,
|
||||
description: 'یخچالدار، ۳۵ کانال اختصاصی، سیستم خنککننده قدرتمند، تحویل نرم، مدیریت آنلاین ۲۴ ساعته.',
|
||||
highlights: ['یخچالدار', '۳۵ کانال اختصاصی', 'تحویل نرم', 'مدیریت آنلاین ۲۴ ساعته'],
|
||||
image: '/images/machine-35-base.webp',
|
||||
@@ -21,8 +21,8 @@ const machinesFa = [
|
||||
id: 'rm-48',
|
||||
name: 'روبات مارکت ۴۸ کانال',
|
||||
shortLabel: '۴۸ کانال',
|
||||
priceLabel: '۴,۴۸۰,۰۰۰,۰۰۰ ریال',
|
||||
priceNumeric: 4480000000,
|
||||
priceLabel: '4,980,000,000 ریال',
|
||||
priceNumeric: 4980000000,
|
||||
description: 'یخچالدار، ۴۸ کانال اختصاصی، قابلیتهای پیشرفته مدیریت آنلاین و تحویل کالا.',
|
||||
highlights: ['یخچالدار', '۴۸ کانال اختصاصی', 'مدیریت آنلاین پیشرفته', 'تحویل هوشمند کالا'],
|
||||
image: '/images/machine-48-base.webp',
|
||||
@@ -33,8 +33,8 @@ const machinesFa = [
|
||||
id: 'rm-60',
|
||||
name: 'روبات مارکت ۶۰ کانال',
|
||||
shortLabel: '۶۰ کانال',
|
||||
priceLabel: '۴,۹۸۰,۰۰۰,۰۰۰ ریال',
|
||||
priceNumeric: 4980000000,
|
||||
priceLabel: '5,480,000,000 ریال',
|
||||
priceNumeric: 5480000000,
|
||||
description: 'یخچالدار، ۶۰ کانال اختصاصی برای کسبوکارهای پرتردد.',
|
||||
highlights: ['یخچالدار', '۶۰ کانال اختصاصی', 'مناسب اماکن پرتردد', 'ظرفیت بالا'],
|
||||
image: '/images/machine-60-base.webp',
|
||||
@@ -48,8 +48,8 @@ const machinesEn = [
|
||||
id: 'rm-35',
|
||||
name: 'Robot Market RM-35',
|
||||
shortLabel: '35 Channels',
|
||||
priceLabel: '3,860,000,000 Rials',
|
||||
priceNumeric: 3860000000,
|
||||
priceLabel: '4,180,000,000 Rials',
|
||||
priceNumeric: 4180000000,
|
||||
description: 'Refrigerated, 35 dedicated channels, powerful cooling system, soft delivery, 24/7 online management.',
|
||||
highlights: ['Refrigerated', '35 dedicated channels', 'Soft delivery', '24/7 online management'],
|
||||
image: '/images/machine-35-base.webp',
|
||||
@@ -60,8 +60,8 @@ const machinesEn = [
|
||||
id: 'rm-48',
|
||||
name: 'Robot Market RM-48',
|
||||
shortLabel: '48 Channels',
|
||||
priceLabel: '4,480,000,000 Rials',
|
||||
priceNumeric: 4480000000,
|
||||
priceLabel: '4,980,000,000 Rials',
|
||||
priceNumeric: 4980000000,
|
||||
description: 'Refrigerated, 48 dedicated channels, advanced online management and delivery capabilities.',
|
||||
highlights: ['Refrigerated', '48 dedicated channels', 'Advanced online management', 'Smart delivery'],
|
||||
image: '/images/machine-48-base.webp',
|
||||
@@ -72,8 +72,8 @@ const machinesEn = [
|
||||
id: 'rm-60',
|
||||
name: 'Robot Market RM-60',
|
||||
shortLabel: '60 Channels',
|
||||
priceLabel: '4,980,000,000 Rials',
|
||||
priceNumeric: 4980000000,
|
||||
priceLabel: '5,480,000,000 Rials',
|
||||
priceNumeric: 5480000000,
|
||||
description: 'Refrigerated, 60 dedicated channels for high-traffic businesses.',
|
||||
highlights: ['Refrigerated', '60 dedicated channels', 'Ideal for high-traffic venues', 'High capacity'],
|
||||
image: '/images/machine-60-base.webp',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
robot-market:
|
||||
build:
|
||||
context: .
|
||||
container_name: robot-market
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
HOSTNAME: 0.0.0.0
|
||||
restart: unless-stopped
|
||||
+9
-8
@@ -1,16 +1,17 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
// AntD ships CJS + uses some patterns that benefit from transpilation in the App Router.
|
||||
transpilePackages: ['antd', '@ant-design/icons', 'rc-util', 'rc-pagination', 'rc-picker'],
|
||||
|
||||
transpilePackages: [
|
||||
'antd',
|
||||
'@ant-design/icons',
|
||||
'rc-util',
|
||||
'rc-pagination',
|
||||
'rc-picker'
|
||||
],
|
||||
|
||||
output: 'export',
|
||||
|
||||
images: {unoptimized: true},
|
||||
output: 'standalone',
|
||||
|
||||
trailingSlash: true,
|
||||
|
||||
@@ -18,4 +19,4 @@ const nextConfig = {
|
||||
assetPrefix: basePath || undefined,
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
module.exports = nextConfig;
|
||||
Reference in New Issue
Block a user